/*
* Copyright (c) 2013-2017 Cinchapi Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cinchapi.concourse.http;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.text.MessageFormat;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.cinchapi.common.reflect.Reflection;
import com.cinchapi.concourse.server.ConcourseServer;
import com.cinchapi.concourse.server.http.HttpServer;
import com.cinchapi.concourse.test.ConcourseIntegrationTest;
import com.cinchapi.concourse.test.Variables;
import com.cinchapi.concourse.time.Time;
import com.cinchapi.concourse.util.Networking;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
/**
* A class that defines methods and basic tests for interacting with the HTTP
* functionality of Concourse Server.
*
* @author Jeff Nelson
*/
public class HttpTest extends ConcourseIntegrationTest {
/**
* Return the response body as the appropriate Java object.
*
* @param response
* @return the response body
*/
protected static <T> T bodyAsJava(Response response, TypeToken<T> type) {
Type type0 = type.getType();
return new Gson().fromJson(bodyAsJson(response), type0);
}
/**
* Return a JsonElement representation of the response body.
*
* @param response
* @param the json response
*/
protected static JsonElement bodyAsJson(Response response) {
try {
String body = response.body().string();
JsonElement json = new JsonParser().parse(body);
Variables.register("json_body_" + response.hashCode(), body);
return json;
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
/**
* Do anything that is necessary to clean up the URL args. For example, make
* sure any records (represented as an int or long) does not get rendered
* using comma separators.
*
* @param args
* @return the clean args
*/
private static Object[] cleanUrlArgs(Object... args) {
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if(arg.getClass() == long.class || arg.getClass() == int.class) {
args[i] = Long.toString((long) arg);
}
else if(arg.getClass() == Long.class
|| arg.getClass() == Integer.class) {
args[i] = arg.toString();
}
}
return args;
}
/**
* Go through the {@code args} and filter out any that shouldn't be
* considered URL args. It is possible that some of the filtered args will
* be passed as arguments to the {@code builder}.
*
* @param builder
* @param args
* @return the filtered args
*/
private static Object[] filterArgs(Request.Builder builder, Object... args) {
List<Object> argsList = Lists.newArrayList(args);
Iterator<Object> it = argsList.iterator();
while (it.hasNext()) {
Object arg = it.next();
if(arg instanceof Headers) {
builder.headers((Headers) arg);
it.remove();
}
}
args = argsList.size() != args.length ? argsList.toArray() : args;
return args;
}
/**
* JSON media type
*/
private static final MediaType JSON = MediaType
.parse("application/json; charset=utf-8");
/**
* The base URL.
*/
private String base = "http://localhost:";
/**
* The HTTP Client.
*/
private OkHttpClient http = new OkHttpClient();
/**
* A reference to the HttpServer that interacts with Concourse Server.
* Subclasses should never need to interact with this directly.
*/
private HttpServer httpServer;
{
// Setup support for cookies
CookieManager cm = new CookieManager();
cm.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
http.setCookieHandler(cm);
}
@Override
public void afterEachTest() {
httpServer.stop();
clearClientCookies();
}
@Override
public void beforeEachTest() {
int port = Networking.getOpenPort();
httpServer = HttpServer.create(
Reflection.<ConcourseServer> get("server", this), port);
httpServer.start();
// Wait for the HTTP server to start
Request req = new Request.Builder().url(base).head().build();
long start = Time.now();
boolean escape = false;
while (!escape) {
try {
http.newCall(req).execute();
escape = true;
}
catch (IOException e) {
escape = TimeUnit.SECONDS.convert(Time.now() - start,
TimeUnit.MICROSECONDS) > 5;
}
}
base += port;
}
/**
* Remove all the client side cookies.
*
* @return {@code true} if the cookies are removed
*/
protected boolean clearClientCookies() {
return ((CookieManager) http.getCookieHandler()).getCookieStore()
.removeAll();
}
/**
* Perform a DELETE request
*
* @param route
* @param args - include a {@link Headers} object to set the request headers
* @return the response
*/
protected Response delete(String route, Object... args) {
try {
Request.Builder builder = new Request.Builder();
args = filterArgs(builder, args);
args = cleanUrlArgs(args);
route = MessageFormat.format(route, args);
Request request = builder.url(base + route).delete().build();
Response response = http.newCall(request).execute();
long ts = response.hashCode();
Variables.register("request_" + ts, request);
Variables.register("response_" + ts, response);
return response;
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
/**
* Perform a GET request
*
* @param route
* @param args - include a {@link Headers} object to set the request headers
* @return the response
*/
protected Response get(String route, Object... args) {
try {
Request.Builder builder = new Request.Builder();
args = filterArgs(builder, args);
args = cleanUrlArgs(args);
route = MessageFormat.format(route, args);
Request request = builder.url(base + route).get().build();
Response response = http.newCall(request).execute();
long ts = response.hashCode();
Variables.register("request_" + ts, request);
Variables.register("response_" + ts, response);
return response;
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
/**
* Perform a login request to the default environment.
*
* @return the response
*/
protected Response login() {
return login("");
}
/**
* Perform a login request.
*
* @param environment
* @return the response
*/
protected Response login(String environment) {
environment = Strings.isNullOrEmpty(environment) ? "" : "/"
+ environment;
JsonObject creds = new JsonObject();
creds.addProperty("username", "admin");
creds.addProperty("password", "admin");
Response resp = post(environment + "/login", creds.toString());
return resp;
}
/**
* Perform a POST request.
*
* @param route
* @param data
* @param args - include a {@link Headers} object to set the request headers
* @return the response
*/
protected Response post(String route, String data, Object... args) {
try {
RequestBody body = RequestBody.create(JSON, data);
Request.Builder builder = new Request.Builder();
args = filterArgs(builder, args);
args = cleanUrlArgs(args);
route = MessageFormat.format(route, args);
Request request = builder.url(base + route).post(body).build();
Response response = http.newCall(request).execute();
long ts = response.hashCode();
Variables.register("request_" + ts, request);
Variables.register("response_" + ts, response);
return response;
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
/**
* Perform a PUT request.
*
* @param route
* @param data
* @param args - include a {@link Headers} object to set the request headers
* @return the response
*/
protected Response put(String route, String data, Object... args) {
try {
RequestBody body = RequestBody.create(JSON, data);
Request.Builder builder = new Request.Builder();
args = filterArgs(builder, args);
args = cleanUrlArgs(args);
route = MessageFormat.format(route, args);
Request request = builder.url(base + route).put(body).build();
Response response = http.newCall(request).execute();
long ts = response.hashCode();
Variables.register("request_" + ts, request);
Variables.register("response_" + ts, response);
return response;
}
catch (IOException e) {
throw Throwables.propagate(e);
}
}
}