Skip to content

Commit

Permalink
Add access to host name in Request API
Browse files Browse the repository at this point in the history
Make HTTP header utility classes more consistent in their usage.

Closes #55
  • Loading branch information
testinfected committed Nov 8, 2016
1 parent 382cbd7 commit d04db79
Show file tree
Hide file tree
Showing 19 changed files with 220 additions and 83 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -11,6 +11,7 @@ This is typically useful for boolean parameters. See `Request#hasParameter` ([#4
- A basic authentication middleware with pluggable authentication provider ([#28])
- Access to request query string. See `Request#query` ([#54])
- Access to request scheme. See `Request#scheme` ([#58])
- Access to server host name as specified in the host header. See `Request#hostname` ([#55])

### Changed
- Request input streams are now closed automatically at the end of the request cycle. This includes file uploads. ([#52])
Expand Down Expand Up @@ -80,6 +81,7 @@ Sessions are considered stale when they have been inactive for longer than the c
[0.9]: https://github.com/testinfected/molecule/compare/v0.9...v0.8.2

[#58]: https://github.com/testinfected/molecule/issues/58
[#55]: https://github.com/testinfected/molecule/issues/55
[#54]: https://github.com/testinfected/molecule/issues/54
[#53]: https://github.com/testinfected/molecule/issues/53
[#52]: https://github.com/testinfected/molecule/issues/52
Expand Down
7 changes: 2 additions & 5 deletions src/main/java/com/vtence/molecule/BodyPart.java
Expand Up @@ -143,10 +143,7 @@ public BodyPart content(InputStream content) {
}

private Charset charset() {
ContentType contentType = ContentType.parse(contentType());
if (contentType == null || contentType.charset() == null) {
return StandardCharsets.UTF_8;
}
return contentType.charset();
if (contentType == null) return StandardCharsets.UTF_8;
return ContentType.parse(contentType).charset(StandardCharsets.UTF_8);
}
}
39 changes: 30 additions & 9 deletions src/main/java/com/vtence/molecule/Request.java
Expand Up @@ -40,9 +40,10 @@ public class Request {
private String path;
private String query;
private String scheme;
private String ip;
private int port;
private String hostName;
private String host;
private String remoteHost;
private String remoteIp;
private int remotePort;
private String protocol;
private InputStream input = new EmptyInputStream();
private HttpMethod method;
Expand Down Expand Up @@ -115,7 +116,7 @@ public Request query(String query) {
* @return the remote client ip
*/
public String remoteIp() {
return ip;
return remoteIp;
}

/**
Expand All @@ -124,7 +125,7 @@ public String remoteIp() {
* @param ip the new ip
*/
public Request remoteIp(String ip) {
this.ip = ip;
this.remoteIp = ip;
return this;
}

Expand All @@ -134,7 +135,7 @@ public Request remoteIp(String ip) {
* @return the remote client hostname
*/
public String remoteHost() {
return hostName;
return remoteHost;
}

/**
Expand All @@ -143,7 +144,7 @@ public String remoteHost() {
* @param hostName the new hostname
*/
public Request remoteHost(String hostName) {
this.hostName = hostName;
this.remoteHost = hostName;
return this;
}

Expand All @@ -153,7 +154,7 @@ public Request remoteHost(String hostName) {
* @return the remote client port
*/
public int remotePort() {
return port;
return remotePort;
}

/**
Expand All @@ -162,7 +163,7 @@ public int remotePort() {
* @param port the new port
*/
public Request remotePort(int port) {
this.port = port;
this.remotePort = port;
return this;
}

Expand All @@ -185,6 +186,26 @@ public Request scheme(String scheme) {
return this;
}

/**
* Gets the host name of the server to which this request was sent.
* It is taken from the Host header value, if any, otherwise the server hostname is used.
*
* @return the server host name
*/
public String hostname() {
return host;
}

/**
* Changes the host name of this request.
*
* @return the new host name
*/
public Request hostname(String name) {
this.host = name;
return this;
}

/**
* Reads the protocol of this request.
*
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/com/vtence/molecule/http/AcceptEncoding.java
Expand Up @@ -11,19 +11,21 @@
import static java.util.stream.Collectors.toList;

public class AcceptEncoding {
private final List<Header.Value> values;
private final List<Header.Value> values = new ArrayList<>();

public static AcceptEncoding of(Request request) {
String header = request.header(ACCEPT_ENCODING);
return header != null ? new AcceptEncoding(header) : new AcceptEncoding("");
return parse(header != null ? header : "");
}

public AcceptEncoding(String header) {
this(new Header(header));
public static AcceptEncoding parse(String header) {
return from(new Header(header));
}

public AcceptEncoding(Header header) {
this.values = header.all();
public static AcceptEncoding from(Header header) {
AcceptEncoding accept = new AcceptEncoding();
accept.values.addAll(header.all());
return accept;
}

public String selectBestEncoding(String... candidates) {
Expand Down
20 changes: 11 additions & 9 deletions src/main/java/com/vtence/molecule/http/AcceptLanguage.java
Expand Up @@ -10,27 +10,29 @@
import java.util.Locale;

import static com.vtence.molecule.http.HeaderNames.ACCEPT_LANGUAGE;
import static java.util.stream.Collectors.toList;

public class AcceptLanguage {
private final List<Locale> locales = new ArrayList<>();

public static AcceptLanguage of(Request request) {
String header = request.header(ACCEPT_LANGUAGE);
return header != null ? new AcceptLanguage(header) : new AcceptLanguage("");
return parse(header != null ? header : "");
}

public AcceptLanguage(String header) {
this(new Header(header));
public static AcceptLanguage parse(String header) {
return AcceptLanguage.from(new Header(header != null ? header : ""));
}

public AcceptLanguage(Header header) {
parseLocales(header);
public static AcceptLanguage from(Header header) {
AcceptLanguage accept = new AcceptLanguage();
header.values().stream()
.filter(locale -> !locale.equals(""))
.map(Locale::forLanguageTag).forEach(accept::add);
return accept;
}

private void parseLocales(Header header) {
locales.addAll(header.values().stream().filter(locale -> !locale.equals(""))
.map(Locale::forLanguageTag).collect(toList()));
private void add(Locale locale) {
this.locales.add(locale);
}

public List<Locale> list() {
Expand Down
24 changes: 16 additions & 8 deletions src/main/java/com/vtence/molecule/http/Authorization.java
Expand Up @@ -6,25 +6,33 @@

public class Authorization {

private final String[] tokens;
private final String scheme;
private final String params;

public static Authorization of(Request request) {
return request.hasHeader(AUTHORIZATION) ? new Authorization(request.header(AUTHORIZATION)) : null;
String header = request.header(AUTHORIZATION);
return header != null ? parse(header) : null;
}

public Authorization(String header) {
this.tokens = header.split(" ");
public static Authorization parse(String header) {
String[] tokens = header.split(" ");
return new Authorization(tokens[0], tokens.length > 1 ? tokens[1]: "");
}

public Authorization(String scheme, String params) {
this.scheme = scheme;
this.params = params;
}

public String scheme() {
return tokens[0];
return scheme;
}

public boolean isForScheme(String scheme) {
return scheme().equals(scheme);
public boolean hasScheme(String scheme) {
return this.scheme.equals(scheme);
}

public String params() {
return tokens.length > 1 ? tokens[1]: "";
return params;
}
}
5 changes: 3 additions & 2 deletions src/main/java/com/vtence/molecule/http/ContentLanguage.java
Expand Up @@ -15,11 +15,12 @@ public class ContentLanguage {
private final List<Locale> locales = new ArrayList<>();

public static ContentLanguage of(Response response) {
return ContentLanguage.parse(response.header(CONTENT_LANGUAGE));
String header = response.header(CONTENT_LANGUAGE);
return header != null ? ContentLanguage.parse(header) : null;
}

public static ContentLanguage parse(String header) {
return header != null ? from(new Header(header)) : from(new Header(""));
return from(new Header(header));
}

public static ContentLanguage from(Header header) {
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/com/vtence/molecule/http/ContentType.java
Expand Up @@ -22,22 +22,24 @@ public ContentType(String type, String subType, String charset) {
this.charset = charset;
}

public static ContentType of(Response response) {
return parse(response.header(CONTENT_TYPE));
public static ContentType of(Request request) {
String header = request.header(CONTENT_TYPE);
return header != null ? parse(header) : null;
}

public static ContentType of(Request request) {
return parse(request.header(CONTENT_TYPE));
public static ContentType of(Response response) {
String header = response.header(CONTENT_TYPE);
return header != null ? parse(header) : null;
}

public static ContentType parse(String header) {
return header != null ? from(new Header(header)) : null;
return from(new Header(header));
}

public static ContentType from(Header header) {
Header.Value contentType = header.first();
String[] tokens = contentType.value().split("/");
return new ContentType(type(tokens), subType(tokens), charset(contentType));
return new ContentType(type(tokens), subType(tokens), charsetOf(contentType));
}

private static String type(String[] tokens) {
Expand All @@ -48,7 +50,7 @@ private static String subType(String[] tokens) {
return tokens.length > 1 ? tokens[SUB_TYPE] : null;
}

private static String charset(Header.Value header) {
private static String charsetOf(Header.Value header) {
return header.parameter("charset");
}

Expand All @@ -65,7 +67,11 @@ public String subType() {
}

public Charset charset() {
return charset != null ? Charset.forName(charset) : null;
return charset(null);
}

public Charset charset(Charset defaultCharset) {
return charset != null ? Charset.forName(charset) : defaultCharset;
}

public String charsetName() {
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/com/vtence/molecule/http/Host.java
@@ -0,0 +1,49 @@
package com.vtence.molecule.http;

import com.vtence.molecule.Request;

import static com.vtence.molecule.http.HeaderNames.HOST;

public class Host {
private final String hostname;
private final Integer port;

public static Host of(Request request) {
String header = request.header(HOST);
return header != null ? parse(header) : null;
}

public static Host parse(String header) {
return new Host(parseHostName(header), parsePort(header));
}

private static String parseHostName(String header) {
if (header.startsWith("[")) {
return header.substring(1, header.indexOf(']'));
}

int colonIndex = header.indexOf(':');
return colonIndex != -1 ? header.substring(0, colonIndex) : header;
}

private static Integer parsePort(String header) {
int colonIndex = header.startsWith("[") ?
header.indexOf(':', header.indexOf(']')) :
header.indexOf(':');

return colonIndex != -1 ? Integer.parseInt(header.substring(colonIndex + 1)) : null;
}

public Host(String hostname, Integer port) {
this.hostname = hostname;
this.port = port;
}

public String name() {
return hostname;
}

public int port(int defaultPort) {
return port != null ? port : defaultPort;
}
}
Expand Up @@ -34,7 +34,7 @@ public void handle(Request request, Response response) throws Exception {
return;
}

if (!auth.isForScheme(BASIC_AUTHENTICATION)) {
if (!auth.hasScheme(BASIC_AUTHENTICATION)) {
response.status(BAD_REQUEST).done();
return;
}
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/com/vtence/molecule/servers/SimpleServer.java
Expand Up @@ -7,6 +7,8 @@
import com.vtence.molecule.Request;
import com.vtence.molecule.Response;
import com.vtence.molecule.Server;
import com.vtence.molecule.http.HeaderNames;
import com.vtence.molecule.http.Host;
import org.simpleframework.http.Part;
import org.simpleframework.http.core.Container;
import org.simpleframework.http.core.ContainerSocketProcessor;
Expand Down Expand Up @@ -111,18 +113,24 @@ private void readInfo(Request request, org.simpleframework.http.Request httpRequ
request.remoteHost(httpRequest.getClientAddress().getHostName());
request.timestamp(httpRequest.getRequestTime());
request.scheme(schemeOf(httpRequest));
request.hostname(hostOf(httpRequest));
request.protocol(String.format("HTTP/%s.%s", httpRequest.getMajor(), httpRequest.getMinor()));
request.secure(httpRequest.isSecure());
request.method(httpRequest.getMethod());
}

private String schemeOf(org.simpleframework.http.Request httpRequest) {
// Prefer the scheme specified in the host header if any
// Prefer the scheme specified in the request line if any
String scheme = httpRequest.getAddress().getScheme();
if (scheme != null) return scheme;
return httpRequest.isSecure() ? "https" : "http";
}

private String hostOf(org.simpleframework.http.Request httpRequest) {
String header = httpRequest.getValue(HeaderNames.HOST);
return header != null ? Host.parse(header).name() : host;
}

private void readHeaders(Request request, org.simpleframework.http.Request httpRequest) {
final List<String> names = httpRequest.getNames();
for (String header : names) {
Expand Down

0 comments on commit d04db79

Please sign in to comment.