Rest API automation using HTTP Client

HTTP Response Status Codes -
HTTP defines forty standard status codes that can be used to convey the results of a client’s request. The status codes are divided into the five categories presented below.

200 (OK)

It indicates that the REST API successfully carried out whatever action the client requested, and that no more specific code in the 2xx series is appropriate.
Unlike the 204 status code, a 200 response should include a response body.The information returned with the response is dependent on the method used in the request, for example:
  • GET an entity corresponding to the requested resource is sent in the response;
  • HEAD the entity-header fields corresponding to the requested resource are sent in the response without any message-body;
  •  POST an entity describing or containing the result of the action;
  • TRACE an entity containing the request message as received by the end server.

201 (Created)

A REST API responds with the 201 status code whenever a resource is created inside a collection. There may also be times when a new resource is created as a result of some controller action, in which case 201 would also be an appropriate response.
The newly created resource can be referenced by the URI(s) returned in the entity of the response, with the most specific URI for the resource given by a Location header field.
The origin server MUST create the resource before returning the 201 status code. If the action cannot be carried out immediately, the server SHOULD respond with 202 (Accepted) response instead.

202 (Accepted)

A 202 response is typically used for actions that take a long while to process. It indicates that request has been accepted for processing, but the processing has not been completed. The request might or might not be eventually acted upon, or even may be disallowed when processing occurs.
Its purpose is to allow a server to accept a request for some other process (perhaps a batch-oriented process that is only run once per day) without requiring that the user agent’s connection to the server persist until the process is completed.
The entity returned with this response SHOULD include an indication of the request’s current status and either a pointer to a status monitor (job queue location) or some estimate of when the user can expect the request to be fulfilled.

204 (No Content)

The 204 status code is usually sent out in response to a PUT, POST, or DELETE request, when the REST API declines to send back any status message or representation in the response message’s body.
An API may also send 204 in conjunction with a GET request to indicate that the requested resource exists, but has no state representation to include in the body.
If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent. This response is primarily intended to allow input for actions to take place without causing a change to the user agent’s active document view, although any new or updated meta-information SHOULD be applied to the document currently in the user agent’s active view.
The 204 response MUST NOT include a message-body, and thus is always terminated by the first empty line after the header fields.

301 (Moved Permanently)

The 301 status code indicates that the REST API’s resource model has been significantly redesigned and a new permanent URI has been assigned to the client’s requested resource. The REST API should specify the new URI in the response’s Location header and all future requests should be directed to the given URI.
You will hardly use this response code in your API as you can always use the API versioning for new API while retaining the old one.

302 (Found)

The HTTP response status code 302 Found is a common way of performing URL redirection. An HTTP response with this status code will additionally provide a URL in the location header field. The user agent (e.g. a web browser) is invited by a response with this code to make a second, otherwise identical, request to the new URL specified in the location field.
Many web browsers implemented this code in a manner that violated this standard, changing the request type of the new request to GET, regardless of the type employed in the original request (e.g. POST). RFC 1945 and RFC 2068 specify that the client is not allowed to change the method on the redirected request. The status codes 303 and 307 have been added for servers that wish to make unambiguously clear which kind of reaction is expected of the client.

303 (See Other)

A 303 response indicates that a controller resource has finished its work, but instead of sending a potentially unwanted response body, it sends the client the URI of a response resource. This can be the URI of a temporary status message, or the URI to some already existing, more permanent, resource.
Generally speaking, the 303 status code allows a REST API to send a reference to a resource without forcing the client to download its state. Instead, the client may send a GET request to the value of the Location header.
The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable.

304 (Not Modified)

This status code is similar to 204 (“No Content”) in that the response body must be empty. The key distinction is that 204 is used when there is nothing to send in the body, whereas 304 is used when the resource has not been modified since the version specified by the request headers If-Modified-Since or If-None-Match.
In such case, there is no need to retransmit the resource since the client still has a previously-downloaded copy.
Using this saves bandwidth and reprocessing on both the server and client, as only the header data must be sent and received in comparison to the entirety of the page being re-processed by the server, then sent again using more bandwidth of the server and client.

307 (Temporary Redirect)

A 307 response indicates that the REST API is not going to process the client’s request. Instead, the client should resubmit the request to the URI specified by the response message’s Location header. However, future requests should still use the original URI.
A REST API can use this status code to assign a temporary URI to the client’s requested resource. For example, a 307 response can be used to shift a client request over to another host.
The temporary URI SHOULD be given by the Location field in the response. Unless the request method was HEAD, the entity of the response SHOULD contain a short hypertext note with a hyperlink to the new URI(s). If the 307 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.

400 (Bad Request)

400 is the generic client-side error status, used when no other 4xx error code is appropriate. Errors can be like malformed request syntax, invalid request message parameters, or deceptive request routing etc.
The client SHOULD NOT repeat the request without modifications.

401 (Unauthorized)

A 401 error response indicates that the client tried to operate on a protected resource without providing the proper authorization. It may have provided the wrong credentials or none at all. The response must include a WWW-Authenticate header field containing a challenge applicable to the requested resource.
The client MAY repeat the request with a suitable Authorization header field. If the request already included Authorization credentials, then the 401 response indicates that authorization has been refused for those credentials. If the 401 response contains the same challenge as the prior response, and the user agent has already attempted authentication at least once, then the user SHOULD be presented the entity that was given in the response, since that entity might include relevant diagnostic information.

403 (Forbidden)

A 403 error response indicates that the client’s request is formed correctly, but the REST API refuses to honor it i.e. the user does not have the necessary permissions for the resource. A 403 response is not a case of insufficient client credentials; that would be 401 (“Unauthorized”).
Authentication will not help and the request SHOULD NOT be repeated. Unlike a 401 Unauthorized response, authenticating will make no difference.

404 (Not Found)

The 404 error status code indicates that the REST API can’t map the client’s URI to a resource but may be available in the future. Subsequent requests by the client are permissible.
No indication is given of whether the condition is temporary or permanent. The 410 (Gone) status code SHOULD be used if the server knows, through some internally configurable mechanism, that an old resource is permanently unavailable and has no forwarding address. This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.

405 (Method Not Allowed)

The API responds with a 405 error to indicate that the client tried to use an HTTP method that the resource does not allow. For instance, a read-only resource could support only GET and HEAD, while a controller resource might allow GET and POST, but not PUT or DELETE.
A 405 response must include the Allow header, which lists the HTTP methods that the resource supports. For example:
Allow: GET, POST

406 (Not Acceptable)

The 406 error response indicates that the API is not able to generate any of the client’s preferred media types, as indicated by the Accept request header. For example, a client request for data formatted as application/xml will receive a 406 response if the API is only willing to format data as application/json.
If the response could be unacceptable, a user agent SHOULD temporarily stop receipt of more data and query the user for a decision on further actions.

412 (Precondition Failed)

The 412 error response indicates that the client specified one or more preconditions in its request headers, effectively telling the REST API to carry out its request only if certain conditions were met. A 412 response indicates that those conditions were not met, so instead of carrying out the request, the API sends this status code.

415 (Unsupported Media Type)

The 415 error response indicates that the API is not able to process the client’s supplied media type, as indicated by the Content-Type request header. For example, a client request including data formatted as application/xml will receive a 415 response if the API is only willing to process data formatted as application/json.
For example, the client uploads an image as image/svg+xml, but the server requires that images use a different format.

500 (Internal Server Error)

500 is the generic REST API error response. Most web frameworks automatically respond with this response status code whenever they execute some request handler code that raises an exception.
A 500 error is never the client’s fault and therefore it is reasonable for the client to retry the exact same request that triggered this response, and hope to get a different response.
API response is generic error message, given when an unexpected condition was encountered and no more specific message is suitable.

501 (Not Implemented)

The server either does not recognize the request method, or it lacks the ability to fulfill the request. Usually this implies future availability (e.g., a new feature of a web-service API).

HTTP Methods -

RESTful APIs enable you to develop any kind of web application having all possible CRUD (create, retrieve, update, delete) operations. REST guidelines suggest you to use specific HTTP method on specific type of call made to server (though technically it is possible to violate this guideline, yet it is highly discouraged).
Use below given information to find suitable HTTP method for the action performed by API.
• HTTP GET
• HTTP POST
• HTTP PUT
• HTTP DELETE

HTTP GET

Use GET requests to retrieve resource representation/information only – and not to modify it in any way. As GET requests do not change the state of resource, these are said to be safe methods. Additionally, GET APIs should be idempotent, which means that making multiple identical requests must produce same result everytime until another API (POST or PUT) has changed the state of resource on server.
If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity in the response and not the source text of the process, unless that text happens to be the output of the process.
For any given HTTP GET API, if resource is found on server then it must return HTTP response code 200 (OK)– along with response body which is usually either XML of JSON content (due to their platform independent nature).
In case resource is NOT found on server then it must return HTTP response code 404 (NOT FOUND). Similarly, if it is determined that GET request itself is not correctly formed then server will return HTTP response code 400 (BAD REQUEST).

HTTP POST

Use POST APIs to create new subordinate resources, e.g. a file is subordinate to a directory containing it or a row is subordinate to a database table. Talking strictly in terms of REST, POST methods are used to create a new resource into the collection of resources.
Ideally, if a resource has been created on the origin server, the response SHOULD be HTTP response code 201 (Created) and contain an entity which describes the status of the request and refers to the new resource.Many times, the action performed by the POST method might not result in a resource that can be identified by a URI. In this case, either HTTP response code 200 (OK) or 204 (No Content) is the appropriate response status.
Please note that POST is neither safe nor idempotent and invoking two identical POST requests will result in two different resources containing the same information (except resource ids).

HTTP PUT

Use PUT APIs primarily to update existing resource (if resource does not exist then API may decide to create a new resource or not). If a new resource has been created by the PUT API, the origin server MUST inform the user agent via the HTTP response code 201 (Created) response and if an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate successful completion of the request.

HTTP DELETE

As the name applies, DELETE APIs are used to delete resources (identified by the Request-URI).
A successful response of DELETE requests SHOULD be HTTP response code 200 (OK) if the response includes an entity describing the status, 202 (Accepted) if the action has been queued, or 204 (No Content)if the action has been performed but the response does not include an entity.
DELETE operations are idempotent. If you DELETE a resource, it’s removed from collection of resource. Repeatedly calling DELETE API on that resource will not change the outcome – however calling DELETE on a resource a second time will return a 404 (NOT FOUND) since it was already removed. Some may argue that it makes DELETE method non-idempotent. It’s matter of discussion and personal opinion.
If the request passes through a cache and the Request-URI identifies one or more currently cached entities, those entries SHOULD be treated as stale. Responses to this method are not cacheable.

Safe Methods

As per HTTP specification, the GET and HEAD methods should be used only for retrieval of resource representations – and they do not update/delete the resource on server. Both methods are said to be considered “safe“.
This allows user agents to represent other methods, such as POST, PUT and DELETE, in a special way, so that the user is made aware of the fact that a possibly unsafe action is being requested – and they can update/delete the resource on server and so should be used carefully.

Idempotent Methods

The term idempotent is used more comprehensively to describe an operation that will produce the same results if executed once or multiple times. This is a very useful property in many situations, as it means that an operation can be repeated or retried as often as necessary without causing unintended effects. With non-idempotent operations, the algorithm may have to keep track of whether the operation was already performed or not.
In HTTP specification, The methods GET, HEAD, PUT and DELETE are declared idempotent methods. Other methods OPTIONS and TRACE SHOULD NOT have side effects so both are also inherently idempotent.

Apache HttpClient -

Apache HttpClient is a robust and complete solution Java library to perform HTTP operations, including RESTful service. In this tutorial, we show you how to create a RESTful Java client with Apache HttpClient, to perform a “GET” and “POST” request. Add these dependency in POM.xml
Get Apache HttpClient
<dependency>
         <groupId>org.apache.httpcomponents</groupId>
         <artifactId>httpclient</artifactId>
         <version>4.1.1</version>
</dependency>

Jackson 2 Dependency

<dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
         <version>2.6.3</version>
</dependency>

Gson Dependency

<dependency>
         <groupId>com.google.code.gson</groupId>
         <artifactId>gson</artifactId>
         <version>2.6.2</version>
</dependency>

GET API Example with Apache HttpClient

public static void demoGetRESTAPI() throws Exception
{
    DefaultHttpClient httpClient = new DefaultHttpClient();
    try
    {
        //Define a HttpGet request; You can choose between HttpPost, HttpDelete or HttpPut also.
        //Choice depends on type of method you will be invoking.
        HttpGet getRequest = new HttpGet("URI");
         
        //Set the API media type in http accept header
        getRequest.addHeader("accept", "application/xml");
          
        //Send the request; It will immediately return the response in HttpResponse object
        HttpResponse response = httpClient.execute(getRequest);
         
        //verify the valid error code first
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != 200)
        {
            throw new RuntimeException("Failed with HTTP error code : " + statusCode);
        }
         
        //Now pull back the response object
        HttpEntity httpEntity = response.getEntity();
        String apiOutput = EntityUtils.toString(httpEntity);
         
        //Lets see what we got from API
        System.out.println(apiOutput); 

       /*
       <user id="10">
           <firstName>demo</firstName>
            <lastName>user</lastName>
       </user>
       */
         
        //In realtime programming, you will need to convert this http response to some java object to re-use it.
        //Lets see how to jaxb unmarshal the api response content

        JAXBContext jaxbContext = JAXBContext.newInstance(User.class);
        Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
        User user = (User) jaxbUnmarshaller.unmarshal(new StringReader(apiOutput));
         
        //Verify the populated object
        System.out.println(user.getId());
        System.out.println(user.getFirstName());
        System.out.println(user.getLastName());
    }
    finally
    {
        //Important: Close the connect
        httpClient.getConnectionManager().shutdown();
    }
}

POST API Example with Apache HttpClient

public static void demoPostRESTAPI() throws Exception
{
    DefaultHttpClient httpClient = new DefaultHttpClient();
     
   // User isa class having getter setters 
    User user = new User();
    user.setId(100);
    user.setFirstName("Lokesh");
    user.setLastName("Gupta");
     
    StringWriter writer = new StringWriter();
    JAXBContext jaxbContext = JAXBContext.newInstance(User.class);
    Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
    jaxbMarshaller.marshal(user, writer);
     
    try
    {
        //Define a postRequest request
        HttpPost postRequest = new HttpPost("URI");
         
        //Set the API media type in http content-type header
        postRequest.addHeader("content-type", "application/xml");
         
        //Set the request post body
        StringEntity userEntity = new StringEntity(writer.getBuffer().toString());
        postRequest.setEntity(userEntity);
          
        //Send the request; It will immediately return the response in HttpResponse object if any
        HttpResponse response = httpClient.execute(postRequest);
         
        //verify the valid error code first
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != 201)
        {
            throw new RuntimeException("Failed with HTTP error code : " + statusCode);
        }
    }
    finally
    {
        //Important: Close the connect
        httpClient.getConnectionManager().shutdown();
    }
}
Using Jackson
Convert Java object to JSON, writeValue(...)  
ObjectMapper mapper = new ObjectMapper();
Staff obj = new Staff();
 
//Object to JSON in file
mapper.writeValue(new File("c:\\file.json"), obj);
 
//Object to JSON in String
String jsonInString = mapper.writeValueAsString(obj);

Convert JSON to Java object, readValue(...)
ObjectMapper mapper = new ObjectMapper();

//JSON from file to Object
Staff obj = mapper.readValue(new File("c:\\file.json"), Staff.class);
 
//JSON from URL to Object
Staff obj = mapper.readValue(new URL("http://xyz.com/staff.json"), Staff.class);
 
//JSON from String to Object
Staff obj = mapper.readValue(jsonInString, Staff.class);

POJO (Plain Old Java Object)

A simple pojo Staff object for testing.
import java.math.BigDecimal;
import java.util.List;

public class Staff {

 private String name;
 private int age;
 private String position;
 private BigDecimal salary;
 private List<String> skills;

 //getters and setters

Java Object to JSON

Convert a Staff object into a JSON formatted string.
import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
 
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
 
public class Jackson2Example {
 
         public static void main(String[] args) {
                 Jackson2Example obj = new Jackson2Example();
                 obj.run();
         }
 
         private void run() {
                 ObjectMapper mapper = new ObjectMapper();
 
                 Staff staff = createDummyObject();
 
                 try {
                          // Convert object to JSON string and save into a file directly
                          mapper.writeValue(new File("D:\\staff.json"), staff);
 
                          // Convert object to JSON string
                          String jsonInString = mapper.writeValueAsString(staff);
                          System.out.println(jsonInString);
 
                          // Convert object to JSON string and pretty print
                          jsonInString = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(staff);
                          System.out.println(jsonInString);
 
                 } catch (JsonGenerationException e) {
                          e.printStackTrace();
                 } catch (JsonMappingException e) {
                          e.printStackTrace();
                 } catch (IOException e) {
                          e.printStackTrace();
                 }
         }
 
         private Staff createDummyObject() {
 
                 Staff staff = new Staff();
 
                 staff.setName("amit");
                 staff.setAge(33);
                 staff.setPosition("Developer");
                 staff.setSalary(new BigDecimal("7500"));
 
                 List<String> skills = new ArrayList<>();
                 skills.add("java");
                 skills.add("python");
 
                 staff.setSkills(skills);
 
                 return staff;
 
         }
 
}


Output

 
{"name":"amit","age":33,"position":"Developer","salary":7500,"skills":["java","python"]}
 
{
  "name" : "amit",
  "age" : 33,
  "position" : "Developer",
  "salary" : 7500,
  "skills" : [ "java", "python" ]
}

JSON to Java Object

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
 
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
 
public class Jackson2Example {
 
         public static void main(String[] args) {
                 Jackson2Example obj = new Jackson2Example();
                 obj.run();
         }
 
         private void run() {
                 ObjectMapper mapper = new ObjectMapper();
 
                 try {
 
                          // Convert JSON string from file to Object
                          Staff staff = mapper.readValue(new File("D:\\staff.json"), Staff.class);
                          System.out.println(staff);
 
                          // Convert JSON to Object
                          Staff staff1 = mapper.readValue(new File("D:\\staff.json"), Staff.class);
                          System.out.println(staff1);
 
                          //Pretty print
                          String prettyStaff1 = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(staff1);
                          System.out.println(prettyStaff1);
 
                 } catch (JsonGenerationException e) {
                          e.printStackTrace();
                 } catch (JsonMappingException e) {
                          e.printStackTrace();
                 } catch (IOException e) {
                          e.printStackTrace();
                 }
         }
 
}


Output

Staff [name=amit, age=33, position=Developer, salary=7500, skills=[java, python]]
 
Staff [name=amit, age=0, position=null, salary=7500, skills=[java, python]]
 
{
  "name" : "amit",
  "age" : 0,
  "position" : null,
  "salary" : 7500,
  "skills" : [ "java", "python" ]
}
We can also assert values from response, below is sample Test case.
import static com.jayway.restassured.RestAssured.get;

import org.json.JSONArray;
import org.json.JSONException;
import org.testng.Assert;
import org.testng.annotations.Test;

import com.jayway.restassured.response.Response;

public class RestTest {

 @Test
 public void getRequestFindCapital() throws JSONException {
  
  //make get request to fetch capital of norway
  Response resp = get("URI");
  
  //Fetching response in JSON
  JSONArray jsonResponse = new JSONArray(resp.asString());
  
  //Fetching value of capital parameter
  String capital = jsonResponse.getJSONObject(0).getString("capital");
  
  //Asserting that capital of Norway is Oslo
  Assert.assertEquals(capital, "Mumbai");
 }
 
}

Comments

  1. This comment has been removed by the author.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. For more details regarding API automation refer below URL - https://www.linkedin.com/pulse/rest-api-automation-using-http-client-amit-tayade/

    ReplyDelete

Post a Comment

Popular posts from this blog

What’s the Difference Between Docker and Kubernetes?

TestNG Listeners

Database Sharding