public void login() {
Response response = httpMessageSender.getResponseFrom("/login"); String authenticity_token =
getAuthenticityTokenFromResponse(response); // post the login form and get the cookies
response = loginUserPost(username, password, authenticity_token); if(response.getStatusCode()==302) {
//get the cookies
loggedInCookieJar = response.getCookies(); }else{
System.out.println(response.asString()); new RuntimeException("Could not login"); }
}
Above code can be summarised as:
Use the HttpMessageSender method to issue a GET request on the /login url.
Parse the response to get the authenticity token.
Post a form message using the authenticity token.
Capture the cookies from the response as loggedInCookieJar to use in future
messages. And in more detail.
About
HttpMessageSenderThe HttpMessageSender GET request issues a GET on the /login url: Response response = httpMessageSender.getResponseFrom("/login");
This is implemented using REST Assured in the HttpMessageSender as follows: public Response getResponseFrom(String endpoint){
return getResponseFrom(endpoint, anEmptyCookieJar() ); }
public Response getResponseFrom(String endpoint,
Map<String, String> cookieJar) { URL theEndPointUrl = createEndPointURL(url, endpoint);
String ct = "text/html,application/xhtml+xml," + "application/xml;q=0.9,*/*;q=0.8";
Response ret = given().
basic(authUser, authPassword). cookies(cookieJar).
contentType(ct).
get(theEndPointUrl.toExternalForm()). andReturn();
return setLastResponse(ret); }
In one respect I’m just using RestAssured as a simple and easy to use HTTP library.
But really, I’m adding abstraction layers to the code to make each class readable and maintainable for the domain that it targets.
HttpMessageSender works at the HTTP domain and sends HTTP messages.
But… this isn’t completely true since it is HTTP messages specifically for the Tracks application.
Hence the reason why you can see the contentType has been configured to xml, because the Tracks API uses XML.
Other points to note:
I pass in the cookie jar that I’m building up across different messages. And this is just a simple Map.
I don’t use any of the REST Assured assertions because I’m using REST Assured to construct and send HTTP messages.
I store the response as lastResponse so that I can retrieve it if I need to.
The method returns a Response which is a RestAssured object. This means that
I haven’t isolated REST Assured to just the HTTP messages, I also use it in the
TrackAppAsApi and TracksApi.
The Basic Auth message header is configured in the messages with
auth().preemptive().basic(authUser, authPassword)
preemptive means that only one request is sent to the server, if you take
this out then you’ll see multiple messages are sent when a request is made. You can find out more about the pre-emptive and challenge modes for Basic
Authentication on the REST Assured website.
github.com/rest-assured/rest-assured/wiki/usage#preemptive-basic- authentication
Now that we know how the message is sent, we have to examine the response and extract the authenticity token.
If I look at an extract of the contents of the response body:
response.body().asString()
The login form in the response looks a bit like this:
<h3>Please log in to use Tracks:</h3>
<div id="database_auth_form" style="display:block">
<form accept-charset="UTF-8" action="/login" method="post"> <div style="display:none">
<input name="utf8" type="hidden" value="✓" /> <input name="authenticity_token" type="hidden"
value="GVnfu2oUd6HvhXf2bEJSfl7U2M0n/TwKZu925E97nuQ=" /> </div> <table> <tr> <td> <label for="user_login">Login:</label> </td> <td>
<input type="text" name="user_login" id="user_login" value="" class="login_text" />
</td> </tr> ...
And you can see the authenticity_token is present as a hidden field.
We want to extract that value so that we can pass it in with our HTTP message.
I extract it using the XmlPath functionality from REST Assured. Which requires me
to use Groovy’s GPath syntax for a query.
private String getAuthenticityTokenFromResponse(
Response response){ // get the authenticity_token from the response
XmlPath htmlParser = response.body().htmlPath(); String auth_token_path =
"**.find {it.@name =='authenticity_token'}.@value"; String authenticity_token = htmlParser.get(auth_token_path); return authenticity_token;
}
This query basically says:
**.find - search all nodes for…
{it.@name =='authenticity_token'} - elements with a name attribute with
.@value - and return the value attribute
I found this syntax quite hard to use initially and it took me some time to find helpful documentation.
Eventually I used a combination of the REST Assured documentation, GPath documentation and StackOverflow to build the query.
github.com/rest-assured/rest-assured/wiki/usage#xml-using-xmlpath blog.jayway.com/2013/04/12/whats-new-in-rest-assured-1-8/ hascode.com/2011/10/ testing-restful-web-services-made-easy-using-the-rest-assured-framework stackoverflow.com/questions/2117739/ what-is-the-full-syntax-of-groovys-gpath-expressions
Sending the
loginForm
response = loginUserPost(username, password, authenticity_token);
The TracksAppAsApi class login method sends the login form by calling the
following private method called loginUserPost. This method uses the
authenticity_token that we extracted from the page returned in the previous HTTP
request.
private Response loginUserPost(String username, String password, String authenticityToken) {
UrlParams params = new UrlParams(); params.add("utf8","%E2%9C%93");
params.addEncoded("authenticity_token", authenticityToken); params.add("user_login", username); params.add("user_password", password); params.add("user_noexpiry", "on"); params.add("login", "Sign+in");
Response response = httpMessageSender.postFormMessageTo(
params.toString(), "/login");
return response; }
You can see that I’m using a UrlParams class that I created to make it easier to build
up a String of parameters without having to encode them or add “&” between all the parameters.
This calls the HttpMessageSender method postFormMessageTo to post the form to
the '/login' end point.
The postFormMessageTo methods call the postMessageTo method but set the
contentType header to a form submission "application/x-www-form-urlencoded" public Response postFormMessageTo(String msg, String endpoint){
return postFormMessageTo(msg, endpoint, anEmptyCookieJar()); }
public Response postFormMessageTo(String msg, String endpoint, Map<String, String> cookieJar){ return postMessageTo(msg, endpoint,
"application/x-www-form-urlencoded", cookieJar); }
And the postMessageTo method sends the actual HTTP message: private Response postMessageTo(String msg, String endpoint, String contentType,
Map<String, String> cookieJar){ URL theEndPointUrl = createEndPointURL(url, endpoint); Response ret =
given().
auth().preemptive().
basic(authUser, authPassword). body(msg).
contentType(contentType). cookies(cookieJar).
when().
post(theEndPointUrl.toExternalForm()). andReturn();
// ignore CREATED UNAUTHORIZED CONFLICT
if( ret.statusCode()!=201 && ret.statusCode()!=401 && ret.statusCode()!=409 ){
System.out.println("POTENTIAL BUG - " +
ret.statusCode() + " FOR " + endpoint + "\n" + msg ); }
return setLastResponse(ret); }
Using RestAssured as an HTTP library. preemptive Basic Authentication is used.
A cookie jar is used to maintain a session across HTTP messages.
I have coded a warning to alert me for the presence of any potential bugs or unexpected response codes, to support my monitoring and testing.