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

org.rapidoid.http.fast.FastHttp Maven / Gradle / Ivy

There is a newer version: 5.5.5
Show newest version
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.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.net.Protocol;
import org.rapidoid.net.abstracts.Channel;
import org.rapidoid.net.impl.RapidoidHelper;
import org.rapidoid.util.U;
import org.rapidoid.wire.Wire;
import org.rapidoid.wrap.BoolWrap;

@Authors("Nikolche Mihajlovski")
@Since("4.3.0")
public class FastHttp implements Protocol {

	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[] 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_X = "Server: X\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 = "Content-Type: text/plain; charset=UTF-8\r\n".getBytes();

	public static final byte[] CONTENT_TYPE_HTML = "Content-Type: text/html; charset=UTF-8\r\n".getBytes();

	public static final byte[] CONTENT_TYPE_JSON = "Content-Type: application/json; charset=UTF-8\r\n".getBytes();

	public static final byte[] CONTENT_TYPE_BINARY = "Content-Type: application/octet-stream\r\n".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 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;

	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 headers = 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, headers, helper);

		boolean processed = false;

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

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

			if (handler.needsParams()) {
				KeyValueRanges paramsKV = helper.pairs1;
				paramsKV.reset();
				HTTP_PARSER.parseParams(buf, paramsKV, query);

				params = U.cast(paramsKV.toMap(buf, true, true));

				if (!isGet.value) {
					KeyValueRanges headersKV = helper.pairs2;
					KeyValueRanges posted = helper.pairs3;
					KeyValueRanges files = helper.pairs4;

					HTTP_PARSER.parsePosted(buf, headersKV, body, posted, files, helper, params);
				}
			}

			if (handler.needsHeadersAndCookies()) {
				// KeyValueRanges cookies = helper.pairs5;
			}

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

		if (!processed) {
			ctx.write(HTTP_404_NOT_FOUND);
		}

		ctx.closeIf(!isKeepAlive.value);
	}

	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 {
				return getHandlers.get(buf, path);
			}

		} 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);

		addHeaders(ctx, isKeepAlive, contentType);
	}

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

		addHeaders(ctx, isKeepAlive, contentType);
	}

	private void addHeaders(Channel ctx, boolean isKeepAlive, byte[] contentType) {
		ctx.write(isKeepAlive ? CONN_KEEP_ALIVE : CONN_CLOSE);

		ctx.write(SERVER_X);

		ctx.write(DATE_IS);
		ctx.write(Dates.getDateTimeBytes());
		ctx.write(CR_LF);

		ctx.write(contentType);
	}

	public void write200(Channel ctx, boolean isKeepAlive, byte[] contentType, byte[] content) {
		start200(ctx, isKeepAlive, contentType);
		writeContent(ctx, content);
	}

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

	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() - 10;
		ctx.write(CR_LF);
		ctx.write(CR_LF);

		int posBefore = out.size();

		JSON.stringify(value, out.asOutputStream());

		int posAfter = out.size();
		out.putNumAsText(posConLen, posAfter - posBefore, false);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy