/*
* Copyright © 2014 Cask Data, 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 co.cask.cdap.client.util;
import co.cask.cdap.client.config.ClientConfig;
import co.cask.cdap.common.UnauthenticatedException;
import co.cask.cdap.security.authentication.client.AccessToken;
import co.cask.common.http.HttpMethod;
import co.cask.common.http.HttpRequest;
import co.cask.common.http.HttpResponse;
import co.cask.http.AbstractHttpHandler;
import co.cask.http.HttpResponder;
import co.cask.http.NettyHttpService;
import com.google.common.base.Charsets;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.AbstractIdleService;
import com.google.gson.Gson;
import com.google.inject.matcher.Matcher;
import org.apache.commons.lang.StringUtils;
import org.jboss.netty.buffer.ChannelBufferInputStream;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.core.HttpHeaders;
import static com.google.inject.matcher.Matchers.any;
import static com.google.inject.matcher.Matchers.only;
public class RESTClientTest {
private static final String ACCESS_TOKEN = "ssdw221e2ffderrfg33322rr";
private static final int RETRY_LIMIT = 2;
private TestHttpService httpService;
private RESTClient restClient;
@Before
public void setUp() throws IOException {
httpService = new TestHttpService();
httpService.startAndWait();
restClient = new RESTClient(ClientConfig.builder().setUnavailableRetryLimit(3).build());
}
@After
public void tearDown() {
httpService.stopAndWait();
}
@Test
public void testPostSuccessWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testPostAuth").toURL();
HttpRequest request = HttpRequest.post(url).build();
HttpResponse response = restClient.execute(request, new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"));
verifyResponse(response, only(200), any(), only("Access token received: " + ACCESS_TOKEN));
}
@Test(expected = UnauthenticatedException.class)
public void testPostUnauthorizedWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testPostAuth").toURL();
HttpRequest request = HttpRequest.post(url).build();
restClient.execute(request, new AccessToken("Unknown", 82000L, "Bearer"));
}
@Test
public void testPutSuccessWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testPutAuth").toURL();
HttpRequest request = HttpRequest.put(url).build();
HttpResponse response = restClient.execute(request, new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"));
verifyResponse(response, only(200), any(), only("Access token received: " + ACCESS_TOKEN));
}
@Test(expected = UnauthenticatedException.class)
public void testPutUnauthorizedWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testPutAuth").toURL();
HttpRequest request = HttpRequest.put(url).build();
restClient.execute(request, new AccessToken("Unknown", 82000L, "Bearer"));
}
@Test
public void testGetSuccessWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testGetAuth").toURL();
HttpRequest request = HttpRequest.get(url).build();
HttpResponse response = restClient.execute(request, new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"));
verifyResponse(response, only(200), any(), only("Access token received: " + ACCESS_TOKEN));
}
@Test(expected = UnauthenticatedException.class)
public void testGetUnauthorizedWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testGetAuth").toURL();
HttpRequest request = HttpRequest.get(url).build();
restClient.execute(request, new AccessToken("Unknown", 82000L, "Bearer"));
}
@Test
public void testDeleteSuccessWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testDeleteAuth").toURL();
HttpRequest request = HttpRequest.delete(url).build();
HttpResponse response = restClient.execute(request, new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"));
verifyResponse(response, only(200), any(), only("Access token received: " + ACCESS_TOKEN));
}
@Test(expected = UnauthenticatedException.class)
public void testDeleteUnauthorizedWithAccessToken() throws Exception {
URL url = getBaseURI().resolve("/api/testDeleteAuth").toURL();
HttpRequest request = HttpRequest.delete(url).build();
restClient.execute(request, new AccessToken("Unknown", 82000L, "Bearer"));
}
@Test
public void testUnavailableLimit() throws Exception {
URL url = getBaseURI().resolve("/api/testUnavail").toURL();
HttpRequest request = HttpRequest.get(url).build();
HttpResponse response = restClient.execute(request, new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"));
verifyResponse(response, only(200), any(), any());
}
@Test
public void testBody() throws Exception {
URL url = getBaseURI().resolve("/api/testCount").toURL();
HttpResponse response = restClient.execute(HttpMethod.POST, url, "increment", null,
new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"), 200);
Assert.assertEquals("1", response.getResponseBodyAsString());
response = restClient.execute(HttpMethod.POST, url, "increment", null,
new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"), 200);
Assert.assertEquals("2", response.getResponseBodyAsString());
response = restClient.execute(HttpMethod.POST, url, "decrement", null,
new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"), 200);
Assert.assertEquals("1", response.getResponseBodyAsString());
response = restClient.execute(HttpMethod.POST, url, "decrement", null,
new AccessToken(ACCESS_TOKEN, 82000L, "Bearer"), 200);
Assert.assertEquals("0", response.getResponseBodyAsString());
}
private void verifyResponse(HttpResponse response, Matcher<Object> expectedResponseCode,
Matcher<Object> expectedMessage, Matcher<Object> expectedBody) {
Assert.assertTrue("Response code - expected: " + expectedResponseCode.toString()
+ " actual: " + response.getResponseCode(),
expectedResponseCode.matches(response.getResponseCode()));
Assert.assertTrue("Response message - expected: " + expectedMessage.toString()
+ " actual: " + response.getResponseMessage(),
expectedMessage.matches(response.getResponseMessage()));
String actualResponseBody = new String(response.getResponseBody());
Assert.assertTrue("Response body - expected: " + expectedBody.toString() + " actual: " + actualResponseBody,
expectedBody.matches(actualResponseBody));
}
private URI getBaseURI() throws URISyntaxException {
InetSocketAddress bindAddress = httpService.getBindAddress();
return new URI("http://" + bindAddress.getHostName() + ":" + bindAddress.getPort());
}
public final class TestHttpService extends AbstractIdleService {
private final NettyHttpService httpService;
public TestHttpService() {
this.httpService = NettyHttpService.builder()
.setHost("localhost")
.addHttpHandlers(Sets.newHashSet(new TestHandler()))
.setWorkerThreadPoolSize(10)
.setExecThreadPoolSize(10)
.setConnectionBacklog(20000)
.build();
}
public InetSocketAddress getBindAddress() {
return httpService.getBindAddress();
}
@Override
protected void startUp() throws Exception {
httpService.startAndWait();
}
@Override
protected void shutDown() throws Exception {
httpService.stopAndWait();
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("bindAddress", httpService.getBindAddress())
.toString();
}
}
@Path("/api")
public final class TestHandler extends AbstractHttpHandler {
private int unavailEnpointCount = 0;
private int integer = 0;
@POST
@Path("/testCount")
public void testIncrement(org.jboss.netty.handler.codec.http.HttpRequest request,
HttpResponder responder) throws Exception {
Reader reader = new InputStreamReader(new ChannelBufferInputStream(request.getContent()), Charsets.UTF_8);
String content = (new Gson()).fromJson(reader, String.class);
if (content.equals("increment")) {
responder.sendString(HttpResponseStatus.OK, String.valueOf(++integer));
} else if (content.equals("decrement")) {
responder.sendString(HttpResponseStatus.OK, String.valueOf(--integer));
} else {
responder.sendString(HttpResponseStatus.OK, String.valueOf(integer));
}
}
@POST
@Path("/testPostAuth")
public void testPostAuth(org.jboss.netty.handler.codec.http.HttpRequest request,
HttpResponder responder) throws Exception {
String authHeaderVal = request.getHeader(HttpHeaders.AUTHORIZATION);
if (("Bearer " + ACCESS_TOKEN).equals(authHeaderVal)) {
responder.sendString(HttpResponseStatus.OK, "Access token received: "
+ request.getHeader(HttpHeaders.AUTHORIZATION).replace("Bearer ", StringUtils.EMPTY));
} else {
responder.sendString(HttpResponseStatus.UNAUTHORIZED, "Access token received: Unknown");
}
}
@PUT
@Path("/testPutAuth")
public void testPutAuth(org.jboss.netty.handler.codec.http.HttpRequest request,
HttpResponder responder) throws Exception {
String authHeaderVal = request.getHeader(HttpHeaders.AUTHORIZATION);
if (("Bearer " + ACCESS_TOKEN).equals(authHeaderVal)) {
responder.sendString(HttpResponseStatus.OK, "Access token received: "
+ request.getHeader(HttpHeaders.AUTHORIZATION).replace("Bearer ", StringUtils.EMPTY));
} else {
responder.sendString(HttpResponseStatus.UNAUTHORIZED, "Access token received: Unknown");
}
}
@DELETE
@Path("/testDeleteAuth")
public void testDeleteAuth(org.jboss.netty.handler.codec.http.HttpRequest request,
HttpResponder responder) throws Exception {
String authHeaderVal = request.getHeader(HttpHeaders.AUTHORIZATION);
if (("Bearer " + ACCESS_TOKEN).equals(authHeaderVal)) {
responder.sendString(HttpResponseStatus.OK, "Access token received: "
+ request.getHeader(HttpHeaders.AUTHORIZATION).replace("Bearer ", StringUtils.EMPTY));
} else {
responder.sendString(HttpResponseStatus.UNAUTHORIZED, "Access token received: Unknown");
}
}
@GET
@Path("/testGetAuth")
public void testGetAuth(org.jboss.netty.handler.codec.http.HttpRequest request,
HttpResponder responder) throws Exception {
String authHeaderVal = request.getHeader(HttpHeaders.AUTHORIZATION);
if (("Bearer " + ACCESS_TOKEN).equals(authHeaderVal)) {
responder.sendString(HttpResponseStatus.OK, "Access token received: "
+ request.getHeader(HttpHeaders.AUTHORIZATION).replace("Bearer ", StringUtils.EMPTY));
} else {
responder.sendString(HttpResponseStatus.UNAUTHORIZED, "Access token received: Unknown");
}
}
@GET
@Path("/testUnavail")
public void testUnavail(org.jboss.netty.handler.codec.http.HttpRequest request,
HttpResponder responder) throws Exception {
unavailEnpointCount++;
//Max number of calls to this endpoint should be 1 (Original request) + RETRY_LIMIT.
if (unavailEnpointCount < (RETRY_LIMIT + 1)) {
responder.sendStatus(HttpResponseStatus.SERVICE_UNAVAILABLE);
} else if (unavailEnpointCount == (RETRY_LIMIT + 1)) {
responder.sendStatus(HttpResponseStatus.OK);
} else {
responder.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
}
}
}