-
Notifications
You must be signed in to change notification settings - Fork 594
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* Custom Header Support #761 Issue: #761 triggered in PR #844 First attempt to offer Custom Header Support Add docs and javadoc * Rename Companion to Factory * apply feedback
- Loading branch information
Showing
4 changed files
with
239 additions
and
0 deletions.
There are no files selected for viewing
47 changes: 47 additions & 0 deletions
47
akka-http-core/src/main/java/akka/http/javadsl/model/headers/ModeledCustomHeader.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
/** | ||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.http.javadsl.model.headers; | ||
|
||
import akka.http.impl.util.Rendering; | ||
import akka.util.Helpers; | ||
|
||
/** | ||
* Support class for building user-defined custom headers defined by implementing `name` and `value`. | ||
* By implementing a {@link ModeledCustomHeader} along with {@link ModeledCustomHeaderFactory} instead of {@link CustomHeader} directly, | ||
* convenience methods that allow parsing this user-defined header from {@link akka.http.javadsl.model.HttpHeader} are | ||
* available to use. | ||
*/ | ||
public abstract class ModeledCustomHeader extends CustomHeader { | ||
|
||
private final String name; | ||
private final String value; | ||
|
||
protected ModeledCustomHeader(final String name, final String value) { | ||
super(); | ||
this.name = name; | ||
this.value = value; | ||
} | ||
|
||
@Override | ||
public String name() { | ||
return name; | ||
} | ||
|
||
@Override | ||
public String lowercaseName() { | ||
return Helpers.toRootLowerCase(name); | ||
} | ||
|
||
@Override | ||
public String value() { | ||
return value; | ||
} | ||
|
||
@Override | ||
@SuppressWarnings("unchecked") | ||
public <R extends Rendering> R render(R r) { | ||
return (R) r.$tilde$tilde(name).$tilde$tilde(':').$tilde$tilde(' ').$tilde$tilde(value); | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
akka-http-core/src/main/java/akka/http/javadsl/model/headers/ModeledCustomHeaderFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
/** | ||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package akka.http.javadsl.model.headers; | ||
|
||
import akka.http.javadsl.model.HttpHeader; | ||
import akka.util.Helpers; | ||
|
||
import java.util.Optional; | ||
|
||
/** | ||
* Companion class for the {@link ModeledCustomHeader} class. It offers methods to create {@link ModeledCustomHeader} | ||
* from {@link String} or {@link HttpHeader}. | ||
*/ | ||
public abstract class ModeledCustomHeaderFactory<H extends ModeledCustomHeader> { | ||
|
||
public abstract String name(); | ||
|
||
public String lowercaseName() { | ||
return Helpers.toRootLowerCase(name()); | ||
} | ||
|
||
/** | ||
* Parses the value checking that the format is correct. | ||
* It may throw if value is not correct | ||
*/ | ||
protected abstract H parse(final String value); | ||
|
||
/** | ||
* Creates a new {@code ModeledCustomHeader} from the value checking that the format is correct. | ||
* It may throw if value is not correct | ||
*/ | ||
public H create(final String value) { | ||
return parse(value); | ||
} | ||
|
||
/** | ||
* Transforms an {@code HttpHeader} to this {@code ModeledCustomHeader} if the name and value are correct. | ||
* It may throw in case of malformed headers | ||
*/ | ||
public Optional<H> from(final HttpHeader header) { | ||
if (header.lowercaseName().equals(lowercaseName())) { | ||
return Optional.of(parse(header.value())); | ||
} | ||
return Optional.empty(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
docs/src/test/java/docs/http/javadsl/CustomHeaderExampleTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
/** | ||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com> | ||
*/ | ||
|
||
package docs.http.javadsl; | ||
|
||
import akka.http.javadsl.model.HttpHeader; | ||
import akka.http.javadsl.model.HttpRequest; | ||
import akka.http.javadsl.model.StatusCodes; | ||
import akka.http.javadsl.model.headers.ModeledCustomHeader; | ||
import akka.http.javadsl.model.headers.ModeledCustomHeaderFactory; | ||
import akka.http.javadsl.model.headers.RawHeader; | ||
import akka.http.javadsl.server.Route; | ||
import akka.http.javadsl.testkit.JUnitRouteTest; | ||
import akka.japi.JavaPartialFunction; | ||
import org.junit.Test; | ||
import scala.PartialFunction; | ||
|
||
import java.util.Optional; | ||
|
||
import static org.junit.Assert.*; | ||
|
||
public class CustomHeaderExampleTest extends JUnitRouteTest { | ||
//#modeled-api-key-custom-header | ||
public static class ApiTokenHeader extends ModeledCustomHeader { | ||
|
||
ApiTokenHeader(String name, String value) { | ||
super(name, value); | ||
} | ||
|
||
public boolean renderInResponses() { | ||
return false; | ||
} | ||
|
||
public boolean renderInRequests() { | ||
return false; | ||
} | ||
|
||
} | ||
|
||
static class ApiTokenHeaderFactory extends ModeledCustomHeaderFactory<ApiTokenHeader> { | ||
|
||
public String name() { | ||
return "apiKey"; | ||
} | ||
|
||
@Override | ||
public ApiTokenHeader parse(String value) { | ||
return new ApiTokenHeader(name(), value); | ||
} | ||
|
||
} | ||
//#modeled-api-key-custom-header | ||
|
||
@Test | ||
public void showCreation() { | ||
//#conversion-creation-custom-header | ||
final ApiTokenHeaderFactory apiTokenHeaderFactory = new ApiTokenHeaderFactory(); | ||
final ApiTokenHeader token = apiTokenHeaderFactory.create("token"); | ||
assertEquals("token", token.value()); | ||
|
||
final HttpHeader header = apiTokenHeaderFactory.create("token"); | ||
assertEquals("apiKey", header.name()); | ||
assertEquals("token", header.value()); | ||
|
||
final Optional<ApiTokenHeader> fromRaw = apiTokenHeaderFactory | ||
.from(RawHeader.create("apiKey", "token")); | ||
assertTrue("Expected a header", fromRaw.isPresent()); | ||
assertEquals("apiKey", fromRaw.get().name()); | ||
assertEquals("token", fromRaw.get().value()); | ||
|
||
// will match, header keys are case insensitive | ||
final Optional<ApiTokenHeader> fromRawUpper = apiTokenHeaderFactory | ||
.from(RawHeader.create("APIKEY", "token")); | ||
assertTrue("Expected a header", fromRawUpper.isPresent()); | ||
assertEquals("apiKey", fromRawUpper.get().name()); | ||
assertEquals("token", fromRawUpper.get().value()); | ||
|
||
// won't match, different header name | ||
final Optional<ApiTokenHeader> wrong = apiTokenHeaderFactory | ||
.from(RawHeader.create("different", "token")); | ||
assertFalse(wrong.isPresent()); | ||
//#conversion-creation-custom-header | ||
} | ||
|
||
@Test | ||
public void testExtraction() { | ||
//#header-value-pf | ||
final ApiTokenHeaderFactory apiTokenHeaderFactory = new ApiTokenHeaderFactory(); | ||
final PartialFunction<HttpHeader, String> extractFromCustomHeader = | ||
new JavaPartialFunction<HttpHeader, String>() { | ||
|
||
@Override | ||
public String apply(HttpHeader header, boolean isCheck) throws Exception { | ||
if (isCheck) | ||
return null; | ||
return apiTokenHeaderFactory.from(header) | ||
.map(apiTokenHeader -> "extracted> " + apiTokenHeader) | ||
.orElseGet(() -> "raw> " + header); | ||
} | ||
}; | ||
|
||
final Route route = headerValuePF(extractFromCustomHeader, this::complete); | ||
|
||
testRoute(route) | ||
.run(HttpRequest.GET("/").addHeader(RawHeader.create("apiKey", "TheKey"))) | ||
.assertStatusCode(StatusCodes.OK) | ||
.assertEntity("extracted> apiKey: TheKey"); | ||
|
||
testRoute(route) | ||
.run(HttpRequest.GET("/").addHeader(RawHeader.create("somethingElse", "TheKey"))) | ||
.assertStatusCode(StatusCodes.OK) | ||
.assertEntity("raw> somethingElse: TheKey"); | ||
|
||
testRoute(route) | ||
.run(HttpRequest.GET("/").addHeader(apiTokenHeaderFactory.create("TheKey"))) | ||
.assertStatusCode(StatusCodes.OK) | ||
.assertEntity("extracted> apiKey: TheKey"); | ||
//#header-value-pf | ||
} | ||
|
||
} |