/*
* COMSAT
* Copyright (c) 2015-2016, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.comsat.webactors.undertow;
import co.paralleluniverse.actors.ActorRef;
import co.paralleluniverse.comsat.webactors.Cookie;
import co.paralleluniverse.comsat.webactors.HttpRequest;
import co.paralleluniverse.comsat.webactors.HttpResponse;
import co.paralleluniverse.comsat.webactors.WebMessage;
import com.google.common.collect.*;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.HeaderMap;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.UnsupportedCharsetException;
import java.util.*;
/**
* @author circlespainter
*/
public final class UndertowHttpRequest extends HttpRequest {
final ActorRef<? super HttpResponse> actorRef;
final HttpServerExchange xch;
private final ByteBuffer reqContent;
private InetSocketAddress sourceAddress;
private ImmutableMultimap<String, String> params;
private URI uri;
private Collection<Cookie> cookies;
private ListMultimap<String, String> heads;
private ByteBuffer byteBufferBody;
private String stringBody;
private String contentType;
private Charset encoding;
public UndertowHttpRequest(ActorRef<? super HttpResponse> actorRef, HttpServerExchange xch, ByteBuffer reqContent) {
this.actorRef = actorRef;
this.xch = xch;
this.reqContent = reqContent;
}
public HttpServerExchange getServerExchange() {
return xch;
}
static ImmutableListMultimap<String, String> extractHeaders(HeaderMap headers) {
if (headers != null) {
final ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
for (final HttpString n : headers.getHeaderNames())
// Normalize header names by their conversion to lower case
builder.putAll(n.toString().toLowerCase(Locale.ENGLISH), headers.get(n));
return builder.build();
}
return null;
}
@Override
public final String getSourceHost() {
fillSourceAddress();
return sourceAddress != null ? sourceAddress.getHostString() : null;
}
@Override
public final int getSourcePort() {
fillSourceAddress();
return sourceAddress != null ? sourceAddress.getPort() : -1;
}
private void fillSourceAddress() {
if (sourceAddress == null)
sourceAddress = xch.getSourceAddress();
}
@Override
public Multimap<String, String> getParameters() {
if (params == null) {
final ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
final Map<String, Deque<String>> parameters = xch.getQueryParameters();
for (final String k : parameters.keySet())
builder.putAll(k, parameters.get(k));
params = builder.build();
}
return params;
}
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public String getScheme() {
initUri();
return uri.getScheme();
}
private void initUri() {
if (uri == null) {
try {
uri = new URI(xch.getRequestURI());
} catch (final URISyntaxException e) {
throw new RuntimeException(e);
}
}
}
@Override
public String getMethod() {
return xch.getRequestMethod().toString();
}
@Override
public String getPathInfo() {
initUri();
return uri.getPath();
}
@Override
public String getContextPath() {
return "/"; // Context path makes sense only for servlets
}
@Override
public String getQueryString() {
initUri();
return uri.getQuery();
}
@Override
public String getRequestURI() {
return xch.getRequestURI();
}
@Override
public String getServerName() {
return xch.getHostName();
}
@Override
public int getServerPort() {
return xch.getHostPort();
}
@SuppressWarnings("unchecked")
@Override
public ActorRef<WebMessage> getFrom() {
return (ActorRef<WebMessage>) actorRef;
}
@Override
public ListMultimap<String, String> getHeaders() {
if (heads == null) {
heads = extractHeaders(xch.getRequestHeaders());
}
return heads;
}
@Override
public Collection<Cookie> getCookies() {
if (cookies == null) {
final ImmutableList.Builder<Cookie> builder = ImmutableList.builder();
for (io.undertow.server.handlers.Cookie c : xch.getRequestCookies().values()) {
builder.add(
Cookie.cookie(c.getName(), c.getValue())
.setComment(c.getComment())
.setDomain(c.getDomain())
.setPath(c.getPath())
.setHttpOnly(c.isHttpOnly())
.setMaxAge(c.getMaxAge())
.setSecure(c.isSecure())
.setVersion(c.getVersion())
.build()
);
}
cookies = builder.build();
}
return cookies;
}
@Override
public int getContentLength() {
return (int) xch.getRequestContentLength();
}
@Override
public Charset getCharacterEncoding() {
if (encoding == null) {
final String charsetName = xch.getRequestCharset();
if (charsetName != null) {
try {
encoding = Charset.forName(charsetName);
} catch (final UnsupportedCharsetException ignored) {}
}
if (encoding == null)
encoding = Charset.defaultCharset();
}
return encoding;
}
@Override
public String getContentType() {
getHeaders();
if (heads != null) {
if (contentType == null) {
final List<String> cts = heads.get(Headers.CONTENT_TYPE_STRING);
if (cts != null && cts.size() > 0)
contentType = cts.get(0);
}
}
return null;
}
@Override
public String getStringBody() {
if (stringBody == null) {
if (byteBufferBody != null)
return null;
decodeStringBody();
}
return stringBody;
}
@Override
public ByteBuffer getByteBufferBody() {
if (byteBufferBody == null) {
if (stringBody != null)
return null;
if (reqContent != null)
byteBufferBody = reqContent;
}
return byteBufferBody;
}
private String decodeStringBody() {
if (reqContent != null) {
try {
//noinspection ConstantConditions
stringBody =
getCharacterEncoding()
.newDecoder()
.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
.decode(reqContent)
.toString();
} catch (final CharacterCodingException ignored) {
}
}
return stringBody;
}
}