
org.rapidoid.http.fast.FastHttp Maven / Gradle / Ivy
package org.rapidoid.http.fast;
/*
* #%L
* rapidoid-http-fast
* %%
* Copyright (C) 2014 - 2015 Nikolche Mihajlovski and contributors
* %%
* 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.
* #L%
*/
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.rapidoid.annotation.Authors;
import org.rapidoid.annotation.Since;
import org.rapidoid.buffer.Buf;
import org.rapidoid.bufstruct.BufMap;
import org.rapidoid.bufstruct.BufMapImpl;
import org.rapidoid.bytes.Bytes;
import org.rapidoid.bytes.BytesUtil;
import org.rapidoid.data.JSON;
import org.rapidoid.data.KeyValueRanges;
import org.rapidoid.data.Range;
import org.rapidoid.data.Ranges;
import org.rapidoid.dates.Dates;
import org.rapidoid.http.Req;
import org.rapidoid.http.fast.handler.FastHttpHandler;
import org.rapidoid.http.fast.handler.FastStaticResourcesHandler;
import org.rapidoid.http.fast.listener.FastHttpListener;
import org.rapidoid.log.Log;
import org.rapidoid.mime.MediaType;
import org.rapidoid.net.Protocol;
import org.rapidoid.net.abstracts.Channel;
import org.rapidoid.net.impl.RapidoidHelper;
import org.rapidoid.u.U;
import org.rapidoid.util.UTILS;
import org.rapidoid.wire.Wire;
import org.rapidoid.wrap.BoolWrap;
@Authors("Nikolche Mihajlovski")
@Since("4.3.0")
public class FastHttp implements Protocol, HttpMetadata {
private static final byte[] HTTP_200_OK = "HTTP/1.1 200 OK\r\n".getBytes();
private static final byte[] HTTP_404_NOT_FOUND = "HTTP/1.1 404 Not Found\r\nContent-Length: 10\r\n\r\nNot found!"
.getBytes();
private static final byte[] HEADER_SEP = ": ".getBytes();
private static final byte[] CONN_KEEP_ALIVE = "Connection: keep-alive\r\n".getBytes();
private static final byte[] CONN_CLOSE = "Connection: close\r\n".getBytes();
private static final byte[] SERVER_HEADER = "Server: Rapidoid\r\n".getBytes();
private static final byte[] CONTENT_LENGTH_IS = "Content-Length: ".getBytes();
static final byte[] CONTENT_LENGTH_UNKNOWN = "Content-Length: ".getBytes();
private static final int CONTENT_LENGTHS_SIZE = 5000;
private static final byte[] DATE_IS = "Date: ".getBytes();
private static final HttpParser HTTP_PARSER = Wire.singleton(HttpParser.class);
private static final byte[] POST = "POST".getBytes();
private static final byte[] PUT = "PUT".getBytes();
private static final byte[] DELETE = "DELETE".getBytes();
private static final byte[] OPTIONS = "OPTIONS".getBytes();
private static final byte[][] CONTENT_LENGTHS = new byte[CONTENT_LENGTHS_SIZE][];
private final HttpResponseCodes responseCodes = new HttpResponseCodes();
private final BufMap getHandlers = new BufMapImpl();
private final BufMap postHandlers = new BufMapImpl();
private final BufMap putHandlers = new BufMapImpl();
private final BufMap deleteHandlers = new BufMapImpl();
private final BufMap optionsHandlers = new BufMapImpl();
private volatile byte[] path1, path2, path3;
private volatile FastHttpHandler handler1, handler2, handler3;
private final List genericHandlers = U.synchronizedList();
private volatile FastHttpHandler staticResourcesHandler = new FastStaticResourcesHandler(this);
private final FastHttpListener listener;
static {
for (int len = 0; len < CONTENT_LENGTHS.length; len++) {
CONTENT_LENGTHS[len] = (new String(CONTENT_LENGTH_IS) + len + new String(CR_LF)).getBytes();
}
}
public FastHttp(FastHttpListener listener) {
this.listener = listener;
}
public synchronized void on(String verb, String path, FastHttpHandler handler) {
if (verb.equals("GET")) {
if (path1 == null) {
path1 = path.getBytes();
handler1 = handler;
} else if (path2 == null) {
path2 = path.getBytes();
handler2 = handler;
} else if (path3 == null) {
path3 = path.getBytes();
handler3 = handler;
} else {
getHandlers.put(path, handler);
}
} else if (verb.equals("POST")) {
postHandlers.put(path, handler);
} else if (verb.equals("PUT")) {
putHandlers.put(path, handler);
} else if (verb.equals("DELETE")) {
deleteHandlers.put(path, handler);
} else if (verb.equals("OPTIONS")) {
optionsHandlers.put(path, handler);
} else {
throw U.rte("Unsupported HTTP verb: %s", verb);
}
}
public void addGenericHandler(FastHttpHandler handler) {
genericHandlers.add(handler);
}
public void removeGenericHandler(FastHttpHandler handler) {
genericHandlers.remove(handler);
}
public void setStaticResourcesHandler(FastHttpHandler staticResourcesHandler) {
this.staticResourcesHandler = staticResourcesHandler;
}
@SuppressWarnings("unchecked")
public void process(Channel channel) {
if (channel.isInitial()) {
return;
}
Buf buf = channel.input();
RapidoidHelper helper = channel.helper();
Range[] ranges = helper.ranges1.ranges;
Ranges hdrs = helper.ranges2;
BoolWrap isGet = helper.booleans[0];
BoolWrap isKeepAlive = helper.booleans[1];
Range xverb = ranges[ranges.length - 1];
Range xuri = ranges[ranges.length - 2];
Range xpath = ranges[ranges.length - 3];
Range xquery = ranges[ranges.length - 4];
Range xprotocol = ranges[ranges.length - 5];
Range xbody = ranges[ranges.length - 6];
HTTP_PARSER.parse(buf, isGet, isKeepAlive, xbody, xverb, xuri, xpath, xquery, xprotocol, hdrs, helper);
// the listener may override all the request dispatching and handler execution
if (!listener.request(this, channel, isGet, isKeepAlive, xbody, xverb, xuri, xpath, xquery, xprotocol, hdrs)) {
return;
}
HttpStatus status = HttpStatus.NOT_FOUND;
FastHttpHandler handler = findFandler(channel, buf, isGet, xverb, xpath);
boolean noReq = (handler != null && !handler.needsParams());
ReqImpl req = null;
if (!noReq) {
KeyValueRanges paramsKV = helper.pairs1.reset();
KeyValueRanges headersKV = helper.pairs2.reset();
HTTP_PARSER.parseParams(buf, paramsKV, xquery);
Map params = U.cast(paramsKV.toMap(buf, true, true));
byte[] body;
Map posted;
Map files;
if (!isGet.value) {
KeyValueRanges postedKV = helper.pairs3.reset();
KeyValueRanges filesKV = helper.pairs4.reset();
body = xbody.bytes(buf);
// parse posted body as data
posted = new HashMap();
HTTP_PARSER.parsePosted(buf, headersKV, xbody, postedKV, filesKV, helper, posted);
files = filesKV.toBinaryMap(buf, true);
} else {
posted = Collections.EMPTY_MAP;
files = Collections.EMPTY_MAP;
body = null;
}
String verb = xverb.str(buf);
String uri = xuri.str(buf);
String path = UTILS.urlDecode(xpath.str(buf));
KeyValueRanges cookiesKV = helper.pairs5.reset();
HTTP_PARSER.parseHeadersIntoKV(buf, hdrs, headersKV, cookiesKV, helper);
Map headers = U.cast(headersKV.toMap(buf, true, true));
Map cookies = U.cast(cookiesKV.toMap(buf, true, true));
MediaType contentType = handler != null ? handler.contentType() : MediaType.HTML_UTF_8;
req = new ReqImpl(this, channel, isKeepAlive.value, verb, uri, path, body, params, headers, cookies,
posted, files, contentType);
}
if (handler != null) {
status = handler.handle(channel, isKeepAlive.value, req);
}
if (status == HttpStatus.NOT_FOUND) {
status = tryGenericHandlers(channel, isKeepAlive.value, req);
}
if (status == HttpStatus.NOT_FOUND) {
channel.write(HTTP_404_NOT_FOUND);
listener.notFound(this, channel, isGet, isKeepAlive, xbody, xverb, xuri, xpath, xquery, xprotocol, hdrs);
}
if (status != HttpStatus.ASYNC) {
channel.closeIf(!isKeepAlive.value);
}
}
private HttpStatus tryGenericHandlers(Channel channel, boolean isKeepAlive, Req req) {
for (FastHttpHandler handler : genericHandlers) {
HttpStatus status = handler.handle(channel, isKeepAlive, req);
if (status != HttpStatus.NOT_FOUND) {
return status;
}
}
return HttpStatus.NOT_FOUND;
}
private FastHttpHandler findFandler(Channel ctx, Buf buf, BoolWrap isGet, Range verb, Range path) {
Bytes bytes = buf.bytes();
if (isGet.value) {
if (path1 != null && BytesUtil.matches(bytes, path, path1, true)) {
return handler1;
} else if (path2 != null && BytesUtil.matches(bytes, path, path2, true)) {
return handler2;
} else if (path3 != null && BytesUtil.matches(bytes, path, path3, true)) {
return handler3;
} else {
FastHttpHandler getHandler = getHandlers.get(buf, path);
if (getHandler == null) {
getHandler = staticResourcesHandler;
}
return getHandler;
}
} else if (BytesUtil.matches(bytes, verb, POST, true)) {
return postHandlers.get(buf, path);
} else if (BytesUtil.matches(bytes, verb, PUT, true)) {
return putHandlers.get(buf, path);
} else if (BytesUtil.matches(bytes, verb, DELETE, true)) {
return deleteHandlers.get(buf, path);
} else if (BytesUtil.matches(bytes, verb, OPTIONS, true)) {
return optionsHandlers.get(buf, path);
}
return null; // no handler
}
public void start200(Channel ctx, boolean isKeepAlive, MediaType contentType) {
ctx.write(HTTP_200_OK);
addDefaultHeaders(ctx, isKeepAlive, contentType);
}
public void startResponse(Channel ctx, int code, boolean isKeepAlive, MediaType contentType) {
ctx.write(responseCodes.get(code));
addDefaultHeaders(ctx, isKeepAlive, contentType);
}
private void addDefaultHeaders(Channel ctx, boolean isKeepAlive, MediaType contentType) {
ctx.write(isKeepAlive ? CONN_KEEP_ALIVE : CONN_CLOSE);
ctx.write(SERVER_HEADER);
ctx.write(DATE_IS);
ctx.write(Dates.getDateTimeBytes());
ctx.write(CR_LF);
ctx.write(contentType.asHttpHeader());
}
void addCustomHeader(Channel ctx, byte[] name, byte[] value) {
ctx.write(name);
ctx.write(HEADER_SEP);
ctx.write(value);
ctx.write(CR_LF);
}
public void write200(Channel ctx, boolean isKeepAlive, MediaType contentTypeHeader, byte[] content) {
start200(ctx, isKeepAlive, contentTypeHeader);
writeContent(ctx, content);
listener.onOkResponse(contentTypeHeader, content);
}
public void write500(Channel ctx, boolean isKeepAlive, MediaType contentTypeHeader, byte[] content) {
startResponse(ctx, 500, isKeepAlive, contentTypeHeader);
writeContent(ctx, content);
listener.onErrorResponse(500, contentTypeHeader, content);
}
public HttpStatus error(Channel ctx, boolean isKeepAlive, Throwable error) {
Log.error("Error while processing request!", error);
startResponse(ctx, 500, isKeepAlive, MediaType.HTML_UTF_8);
writeContent(ctx, HttpUtils.getErrorMessage(error).getBytes());
return HttpStatus.ERROR;
}
private void writeContent(Channel ctx, byte[] content) {
int len = content.length;
if (len < CONTENT_LENGTHS_SIZE) {
ctx.write(CONTENT_LENGTHS[len]);
} else {
ctx.write(CONTENT_LENGTH_IS);
Buf out = ctx.output();
out.putNumAsText(out.size(), len, true);
ctx.write(CR_LF);
}
ctx.write(CR_LF);
ctx.write(content);
}
public void writeSerializedJson(Channel ctx, boolean isKeepAlive, Object value) {
start200(ctx, isKeepAlive, MediaType.JSON_UTF_8);
Buf out = ctx.output();
ctx.write(CONTENT_LENGTH_UNKNOWN);
int posConLen = out.size();
ctx.write(CR_LF);
// finishing the headers
ctx.write(CR_LF);
int posBefore = out.size();
JSON.stringify(value, out.asOutputStream());
int posAfter = out.size();
int contentLength = posAfter - posBefore;
out.putNumAsText(posConLen, contentLength, false);
}
public void done(Channel ctx, boolean isKeepAlive) {
ctx.done();
ctx.closeIf(!isKeepAlive);
}
public FastHttpListener getListener() {
return listener;
}
public synchronized void clearHandlers() {
path1 = path2 = path3 = null;
handler1 = handler2 = handler3 = null;
getHandlers.clear();
postHandlers.clear();
putHandlers.clear();
deleteHandlers.clear();
optionsHandlers.clear();
genericHandlers.clear();
}
public void writeResult(Channel ctx, boolean isKeepAlive, Object result, MediaType contentType) {
if (contentType.equals(MediaType.JSON_UTF_8)) {
if (result instanceof byte[]) {
write200(ctx, isKeepAlive, contentType, (byte[]) result);
} else {
writeSerializedJson(ctx, isKeepAlive, result);
}
} else {
byte[] response = UTILS.toBytes(result);
write200(ctx, isKeepAlive, contentType, response);
}
}
public void renderBody(Channel ctx, int code, MediaType contentType, Object result) {
byte[] bytes;
if (U.eq(contentType, MediaType.JSON_UTF_8)) {
if (result instanceof byte[]) {
bytes = (byte[]) result;
} else {
bytes = JSON.stringify(result).getBytes();
}
} else {
bytes = UTILS.toBytes(result);
}
ctx.write(bytes);
if (code == 200) {
listener.onOkResponse(contentType, bytes);
} else {
listener.onErrorResponse(code, contentType, bytes);
}
}
public void notFound(Channel ctx, boolean isKeepAlive, FastHttpHandler fromHandler, Req req) {
int count = genericHandlers.size();
HttpStatus status = HttpStatus.NOT_FOUND;
for (int i = 0; i < count; i++) {
FastHttpHandler handler = genericHandlers.get(i);
if (handler == fromHandler) {
if (i < count - 1) {
FastHttpHandler nextHandler = genericHandlers.get(i + 1);
status = nextHandler.handle(ctx, isKeepAlive, req);
break;
}
}
}
if (status == HttpStatus.NOT_FOUND) {
ctx.write(HTTP_404_NOT_FOUND);
done(ctx, isKeepAlive);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy