Skip to content

Commit

Permalink
openwhisk web action fixes; #20
Browse files Browse the repository at this point in the history
- handle query parameters
- add custom body deserializer that handles empty objects being send on
GET requests
  • Loading branch information
bbilger committed Jun 24, 2017
1 parent 947da36 commit 8d4fe16
Show file tree
Hide file tree
Showing 11 changed files with 418 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import org.junit.Test;

import com.jrestless.test.UtilityClassCodeCoverageBumper;

public class AwsAuthenticationSchemesTest {

@Test
Expand All @@ -23,4 +25,9 @@ public void testAllAwsAuthenticationSchemesContainsAllAuthenticationSchemes()
authenticationSchemes.removeAll(AwsAuthenticationSchemes.ALL_AWS_AUTHENTICATION_SCHEMES);
assertTrue(authenticationSchemes.isEmpty());
}

@Test
public void bumpCodeCoverageByInvokingThePrivateConstructor() {
UtilityClassCodeCoverageBumper.invokePrivateConstructor(AwsAuthenticationSchemes.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public interface WebActionRequest {
/**
* The HTTP method of the request.
*/
String getHttpMethod();
String getMethod();

/**
* The request headers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public JsonObject delegateJsonRequest(@Nonnull JsonObject request) {
@Override
protected JRestlessContainerRequest createContainerRequest(WebActionRequest request) {
requireNonNull(request);
final String httpMethod = requireNonNull(request.getHttpMethod(), "httpMethod must be given").toUpperCase();
final String httpMethod = requireNonNull(request.getMethod(), "httpMethod must be given").toUpperCase();
final String body = request.getBody();
final Map<String, String> requestHeaders = request.getHeaders();
final Map<String, List<String>> containerRequestHeaders;
Expand All @@ -118,22 +118,24 @@ protected JRestlessContainerRequest createContainerRequest(WebActionRequest requ

protected RequestAndBaseUri getRequestAndBaseUri(WebActionRequest request) {
final String path = request.getPath();
final URI requestUri;
/*
* prepend "/"
*/
String generatedRequestUri;
// prepend "/"
if (path == null || path.isEmpty()) {
requestUri = URI.create("/");
generatedRequestUri = "/";
} else if (!path.startsWith("/")) {
requestUri = URI.create("/" + path);
generatedRequestUri = "/" + path;
} else {
requestUri = URI.create(path);
generatedRequestUri = path;
}
// append query parameters
if (request.getQuery() != null && !request.getQuery().isEmpty()) {
generatedRequestUri += "?" + request.getQuery();
}
/*
* we have to use "/" as base URI since there is no proper way to get
* the base URI from the request
*/
return new RequestAndBaseUri(URI.create("/"), requestUri);
return new RequestAndBaseUri(URI.create("/"), URI.create(generatedRequestUri));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.util.Map;
import java.util.Objects;

import com.google.gson.annotations.JsonAdapter;
import com.google.gson.annotations.SerializedName;

/**
Expand All @@ -30,25 +31,34 @@
*
*/
public final class DefaultWebActionRequest implements WebActionRequest {
@SerializedName("__ow_method")
private String httpMethod;
@SerializedName("__ow_headers")

static final String SERIALIZED_METHOD_NAME = "__ow_method";
static final String SERIALIZED_HEADERS_NAME = "__ow_headers";
static final String SERIALIZED_PATH_NAME = "__ow_path";
static final String SERIALIZED_USER_NAME = "__ow_user";
static final String SERIALIZED_BODY_NAME = "__ow_body";
static final String SERIALIZED_QUERY_NAME = "__ow_query";

@SerializedName(SERIALIZED_METHOD_NAME)
private String method;
@SerializedName(SERIALIZED_HEADERS_NAME)
private Map<String, String> headers;
@SerializedName("__ow_path")
@SerializedName(SERIALIZED_PATH_NAME)
private String path;
@SerializedName("__ow_user")
@SerializedName(SERIALIZED_USER_NAME)
private String user;
@SerializedName("__ow_body")
@JsonAdapter(WebActionRequestBodyAdapter.class)
@SerializedName(SERIALIZED_BODY_NAME)
private String body;
@SerializedName("__ow_query")
@SerializedName(SERIALIZED_QUERY_NAME)
private String query;

public DefaultWebActionRequest() {
}
// for testing, only
DefaultWebActionRequest(String httpMethod, Map<String, String> headers, String path, String user, String body,
DefaultWebActionRequest(String method, Map<String, String> headers, String path, String user, String body,
String query) {
this.httpMethod = httpMethod;
this.method = method;
this.headers = headers;
this.path = path;
this.user = user;
Expand All @@ -57,8 +67,8 @@ public DefaultWebActionRequest() {
}

@Override
public String getHttpMethod() {
return httpMethod;
public String getMethod() {
return method;
}

@Override
Expand Down Expand Up @@ -98,13 +108,13 @@ public boolean equals(final Object other) {
return false;
}
DefaultWebActionRequest castOther = (DefaultWebActionRequest) other;
return Objects.equals(httpMethod, castOther.httpMethod) && Objects.equals(headers, castOther.headers)
return Objects.equals(method, castOther.method) && Objects.equals(headers, castOther.headers)
&& Objects.equals(path, castOther.path) && Objects.equals(user, castOther.user)
&& Objects.equals(body, castOther.body) && Objects.equals(query, castOther.query);
}

@Override
public int hashCode() {
return Objects.hash(httpMethod, headers, path, user, body, query);
return Objects.hash(method, headers, path, user, body, query);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2017 Bjoern Bilger
*
* 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.jrestless.openwhisk.webaction.io;

import java.lang.reflect.Type;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

/**
* Special request body deserializer.
* <p>
* A deserializer is required because for GET request the body is given as an
* empty object instead of null or undefined. Note: when a body is passed in a
* POST or PUT request it's always a string, when the Web Action is handle raw
* http.
* <p>
* The serializer is required for testing, only.
*
* @author Bjoern Bilger
*
*/
class WebActionRequestBodyAdapter implements JsonDeserializer<String>, JsonSerializer<String> {

@Override
public String deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
if (json.isJsonNull()) {
return null;
} else if (json.isJsonPrimitive()) {
return json.getAsString();
} else if (json.isJsonObject() && json.getAsJsonObject().size() == 0) {
/*
* for some reason OpenWhisk passes an empty object on GET requests
* if the Web Action is defined as "raw", even though it should pass a string!
* => handle this case by returning null
*/
return null;
}
throw new IllegalStateException(
"expected a string, null, or an empty object as request body but got" + this.getClass());
}

@Override
public JsonElement serialize(String src, Type typeOfSrc, JsonSerializationContext context) {
return new JsonPrimitive(src);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
Expand Down Expand Up @@ -162,7 +163,7 @@ public void testPostJsonAndGetString() {
JsonObject request = new WebActionRequestBuilder()
.setHttpMethod(HttpMethod.POST)
.setPath("post-json-get-string")
.setBodyBase64Encoded("{\"value\":\"123\"}")
.setBodyBase64Encoded("{\"value\": \"123\"}")
.setContentType(MediaType.APPLICATION_JSON)
.buildJson();
JsonObject actualResponse = handler.delegateJsonRequest(request);
Expand Down Expand Up @@ -224,6 +225,20 @@ public void testWebActionRequestMemberInjection() {
inOrder.verify(testService).injectedStringArg(requestBuilder1.build().getPath());
}

@Test
public void testQueryParameters() {
JsonObject request = new WebActionRequestBuilder()
.setHttpMethod(HttpMethod.GET)
.setPath("/inject-query-params")
.setQuery("q1=1&q2=2")
.buildJson();
JsonObject actualResponse = handler.delegateJsonRequest(request);
assertEquals(WebActionHttpResponseBuilder.noContent(), actualResponse);
InOrder inOrder = Mockito.inOrder(testService);
inOrder.verify(testService).injectedStringArg("1");
inOrder.verify(testService).injectedStringArg("2");
}

@Path("/")
@Singleton // singleton in order to test proxies
public static class TestResource {
Expand Down Expand Up @@ -310,17 +325,24 @@ public void injectWebActionRequest(@Context WebActionRequest request) {
testService.injectedWebActionRequest(request);
}

@Path("/inject-webaction-request-member0")
@GET
@Path("/inject-webaction-request-member0")
public void injectWebActionRequestAsMember0() {
testService.injectedStringArg(webActionRequestMember.getPath());
}

@Path("/inject-webaction-request-member1")
@GET
@Path("/inject-webaction-request-member1")
public void injectWebActionRequestAsMember1() {
testService.injectedStringArg(webActionRequestMember.getPath());
}

@GET
@Path("/inject-query-params")
public void injectQuerieParams(@QueryParam("q1") String q1, @QueryParam("q2") String q2) {
testService.injectedStringArg(q1);
testService.injectedStringArg(q2);
}
}

public static interface TestService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public void createContainerRequest_NullHeadersGiven_ShouldMapToEmptyHeaders() {
@Test
public void createContainerRequest_NullBodyGiven_ShouldMapToEmptyInputStream() {
WebActionRequest request = minimalRequestBuilder()
.setRawBody(null)
.setBody(null)
.build();
JRestlessContainerRequest containerRequest = handler.createContainerRequest(request);
assertArrayEquals(new byte[0], IOUtils.toBytes(containerRequest.getEntityStream()));
Expand All @@ -143,7 +143,7 @@ public void createContainerRequest_NullBodyGiven_ShouldMapToEmptyInputStream() {
@Test
public void createContainerRequest_BodyGiven_ShouldMapToInputStream() {
WebActionRequest request = minimalRequestBuilder()
.setRawBody("some body")
.setBody("some body")
.build();
JRestlessContainerRequest containerRequest = handler.createContainerRequest(request);
assertArrayEquals("some body".getBytes(), IOUtils.toBytes(containerRequest.getEntityStream()));
Expand Down Expand Up @@ -200,6 +200,28 @@ public void createContainerRequest_AbsolutePathGiven_ShouldMapRequestPathAsIs()
assertEquals(URI.create("/"), containerRequest.getBaseUri());
}

@Test
public void createContainerRequest_PathAndEmpyQueryGiven_ShouldNotAppendQuery() {
WebActionRequest request = minimalRequestBuilder()
.setPath("/path")
.setQuery("")
.build();
JRestlessContainerRequest containerRequest = handler.createContainerRequest(request);
assertEquals(URI.create("/path"), containerRequest.getRequestUri());
assertEquals(URI.create("/"), containerRequest.getBaseUri());
}

@Test
public void createContainerRequest_PathAndQueryGiven_ShouldNotAppendQuery() {
WebActionRequest request = minimalRequestBuilder()
.setPath("/path")
.setQuery("a=b")
.build();
JRestlessContainerRequest containerRequest = handler.createContainerRequest(request);
assertEquals(URI.create("/path?a=b"), containerRequest.getRequestUri());
assertEquals(URI.create("/"), containerRequest.getBaseUri());
}

@Test
public void onRequestFailure_ShouldCreate500() {
JsonObject response = handler.onRequestFailure(null, null, null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.jrestless.openwhisk.webaction.io;

import org.junit.Test;

import com.jrestless.test.UtilityClassCodeCoverageBumper;

public class BinaryMediaTypeDetectorTest {
@Test
public void bumpCodeCoverageByInvokingThePrivateConstructor() {
UtilityClassCodeCoverageBumper.invokePrivateConstructor(BinaryMediaTypeDetector.class);
}
}
Loading

0 comments on commit 8d4fe16

Please sign in to comment.