All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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.Map;
import java.util.Map.Entry;

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.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.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_500_ERROR = "HTTP/1.1 500 Internal Server Error\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();

	private 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();

	public static final byte[] CONTENT_TYPE_PLAIN = MediaType.PLAIN_TEXT_UTF_8.asHttpHeader();

	public static final byte[] CONTENT_TYPE_HTML = MediaType.HTML_UTF_8.asHttpHeader();

	public static final byte[] CONTENT_TYPE_JSON = MediaType.JSON_UTF_8.asHttpHeader();

	public static final byte[] CONTENT_TYPE_BINARY = MediaType.BINARY.asHttpHeader();

	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 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 byte[] path1, path2, path3;

	private FastHttpHandler handler1, handler2, handler3;

	private final FastHttpHandler staticResourcesHandler = new FastStaticResourcesHandler(this);

	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 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 process(Channel ctx) {
		if (ctx.isInitial()) {
			return;
		}

		Buf buf = ctx.input();
		RapidoidHelper helper = ctx.helper();

		Range[] ranges = helper.ranges1.ranges;
		Ranges hdrs = helper.ranges2;

		BoolWrap isGet = helper.booleans[0];
		BoolWrap isKeepAlive = helper.booleans[1];

		Range verb = ranges[ranges.length - 1];
		Range uri = ranges[ranges.length - 2];
		Range path = ranges[ranges.length - 3];
		Range query = ranges[ranges.length - 4];
		Range protocol = ranges[ranges.length - 5];
		Range body = ranges[ranges.length - 6];

		HTTP_PARSER.parse(buf, isGet, isKeepAlive, body, verb, uri, path, query, protocol, hdrs, helper);

		HttpStatus status = HttpStatus.NOT_FOUND;

		FastHttpHandler handler = findFandler(ctx, buf, isGet, verb, path);

		if (handler != null) {
			Map params = null;

			if (handler.needsParams()) {
				params = U.map();

				KeyValueRanges paramsKV = helper.pairs1.reset();
				KeyValueRanges headersKV = helper.pairs2.reset();

				HTTP_PARSER.parseParams(buf, paramsKV, query);

				// parse URL parameters as data
				Map data = U.cast(paramsKV.toMap(buf, true, true));

				if (!isGet.value) {
					KeyValueRanges postedKV = helper.pairs3.reset();
					KeyValueRanges filesKV = helper.pairs4.reset();

					// parse posted body as data
					HTTP_PARSER.parsePosted(buf, headersKV, body, postedKV, filesKV, helper, data);
				}

				// filter special data values
				Map special = findSpecialData(data);

				if (special != null) {
					data.keySet().removeAll(special.keySet());
				} else {
					special = U.cast(Collections.EMPTY_MAP);
				}

				// put all data directly as parameters
				params.putAll(data);

				params.put(DATA, data);
				params.put(SPECIAL, special);

				// finally, the HTTP info
				params.put(VERB, verb.str(buf));
				params.put(URI, uri.str(buf));
				params.put(PATH, path.str(buf));

				params.put(CLIENT_ADDRESS, ctx.address());

				if (handler.needsHeadersAndCookies()) {
					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));

					params.put(HEADERS, headers);
					params.put(COOKIES, cookies);

					params.put(HOST, U.get(headers, "Host", null));
					params.put(FORWARDED_FOR, U.get(headers, "X-Forwarded-For", null));
				}
			}

			status = handler.handle(ctx, isKeepAlive.value, params);
		}

		if (status == HttpStatus.NOT_FOUND) {
			ctx.write(HTTP_404_NOT_FOUND);
		}

		if (status != HttpStatus.ASYNC) {
			ctx.closeIf(!isKeepAlive.value);
		}
	}

	private Map findSpecialData(Map data) {
		Map special = null;

		for (Entry param : data.entrySet()) {
			String name = param.getKey();

			if (name.startsWith("$")) {
				special = U.safe(special);
				special.put(name, param.getValue());
			}
		}

		return special;
	}

	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, byte[] contentType) {
		ctx.write(HTTP_200_OK);

		addDefaultHeaders(ctx, isKeepAlive, contentType);
	}

	private void start500(Channel ctx, boolean isKeepAlive, byte[] contentType) {
		ctx.write(HTTP_500_ERROR);

		addDefaultHeaders(ctx, isKeepAlive, contentType);
	}

	private void addDefaultHeaders(Channel ctx, boolean isKeepAlive, byte[] 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);
	}

	private 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, byte[] contentTypeHeader, byte[] content) {
		start200(ctx, isKeepAlive, contentTypeHeader);
		writeContent(ctx, content);
	}

	public void write500(Channel ctx, boolean isKeepAlive, byte[] contentTypeHeader, byte[] content) {
		start500(ctx, isKeepAlive, contentTypeHeader);
		writeContent(ctx, content);
	}

	public HttpStatus error(Channel ctx, boolean isKeepAlive, Throwable error) {
		Log.error("Error while processing request!", error);

		start500(ctx, isKeepAlive, CONTENT_TYPE_HTML);
		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, CONTENT_TYPE_JSON);

		Buf out = ctx.output();

		ctx.write(CONTENT_LENGTH_UNKNOWN);

		int posConLen = out.size();
		ctx.write(CR_LF);
		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 addCookie(Channel ctx, String name, String value, String... extras) {
		value = HttpUtils.cookieValueWithExtras(value, extras);
		String cookie = name + "=" + value;
		addCustomHeader(ctx, HttpHeaders.SET_COOKIE.getBytes(), cookie.getBytes());
	}

	public void done(Channel ctx, boolean isKeepAlive) {
		ctx.done();
		ctx.closeIf(!isKeepAlive);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy