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

org.slingerxv.limitart.net.http.HttpServer Maven / Gradle / Ivy

/*
 * Copyright (c) 2016-present The Limitart Project
 *
 * 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.
 */
package org.slingerxv.limitart.net.http;

import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpMethod.POST;

import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slingerxv.limitart.collections.ConstraintMap;
import org.slingerxv.limitart.funcs.Proc1;
import org.slingerxv.limitart.funcs.Proc2;
import org.slingerxv.limitart.funcs.Procs;
import org.slingerxv.limitart.net.AbstractNettyServer;
import org.slingerxv.limitart.net.IServer;
import org.slingerxv.limitart.net.http.codec.QueryStringDecoderV2;
import org.slingerxv.limitart.net.http.constant.QueryMethod;
import org.slingerxv.limitart.net.http.constant.RequestErrorCode;
import org.slingerxv.limitart.net.http.handler.HttpHandler;
import org.slingerxv.limitart.net.http.message.UrlMessage;
import org.slingerxv.limitart.net.http.message.UrlMessageFactory;
import org.slingerxv.limitart.net.http.util.HttpUtil;
import org.slingerxv.limitart.util.StringUtil;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;

/**
 * Http服务器
 * 
 * @author Hank
 *
 */
@Sharable
public class HttpServer extends AbstractNettyServer implements IServer {
	private static Logger log = LoggerFactory.getLogger(HttpServer.class);
	// config
	private String serverName;
	private int port;
	// 消息聚合最大(1024KB),即Content-Length
	private int httpObjectAggregatorMax;
	private UrlMessageFactory facotry;
	private Set whiteList;
	// listener
	private Proc1 onServerBind;
	private Proc2 onChannelStateChanged;
	private Proc2> dispatchMessage;
	private Proc2 onMessageOverSize;
	private Proc2 onExceptionCaught;

	private HttpServer(HttpServerBuilder builder) {
		super(builder.serverName);
		this.port = builder.port;
		this.httpObjectAggregatorMax = builder.httpObjectAggregatorMax;
		this.serverName = builder.serverName;
		this.whiteList = builder.whiteList;
		Objects.requireNonNull(builder.facotry, "factory");
		this.facotry = builder.facotry;
		this.onServerBind = builder.onServerBind;
		this.onChannelStateChanged = builder.onChannelStateChanged;
		this.dispatchMessage = builder.dispatchMessage;
		this.onMessageOverSize = builder.onMessageOverSize;
		this.onExceptionCaught = builder.onExceptionCaught;
	}

	@Override
	protected void initPipeline(ChannelPipeline pipeline) {
		pipeline.addLast(new HttpServerCodec()).addLast(new HttpObjectAggregator(httpObjectAggregatorMax) {
			@Override
			protected void handleOversizedMessage(ChannelHandlerContext ctx, HttpMessage oversized) throws Exception {
				Exception e = new Exception(ctx.channel() + " : " + oversized + " is over size");
				log.error(e.getMessage(), e);
				Procs.invoke(onMessageOverSize, ctx.channel(), oversized);
			}
		}).addLast(new HttpContentCompressor());
	}

	@Override
	protected void exceptionCaught0(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		Procs.invoke(onExceptionCaught, ctx.channel(), cause);
	}

	@Override
	protected void channelActive0(ChannelHandlerContext ctx) throws Exception {
		if (whiteList != null && !whiteList.isEmpty()) {
			InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
			String remoteAddress = insocket.getAddress().getHostAddress();
			if (!whiteList.contains(remoteAddress)) {
				ctx.channel().close();
				log.error("ip: " + remoteAddress + " rejected link!");
				return;
			}
		}
		Procs.invoke(onChannelStateChanged, ctx.channel(), true);
	}

	@Override
	protected void channelInactive0(ChannelHandlerContext ctx) throws Exception {
		Procs.invoke(onChannelStateChanged, ctx.channel(), false);
	}

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, Object arg) throws Exception {
		FullHttpRequest msg = (FullHttpRequest) arg;
		if (!msg.decoderResult().isSuccess()) {
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_DECODE_FAIL);
			return;
		}
		if (StringUtil.isEmptyOrNull(msg.uri())) {
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_URL_EMPTY);
			return;
		}
		String url;
		ConstraintMap params = ConstraintMap.empty();
		if (msg.method() == GET) {
			QueryStringDecoderV2 queryStringDecoder = new QueryStringDecoderV2(msg.uri());
			url = queryStringDecoder.path();
			params = queryStringDecoder.parameters();
		} else if (msg.method() == POST) {
			url = msg.uri();
		} else {
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_METHOD_FORBBIDEN);
			return;
		}
		if ("/2016info".equals(url)) {
			HttpUtil.sendResponse(ctx.channel(), HttpResponseStatus.OK, "hello~stupid!", true);
			return;
		}
		UrlMessage message = facotry.getMessage(url);
		if (message == null) {
			log.error("消息不存在:" + url);
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_URL_FORBBIDEN);
			return;
		}
		if (message.getMethod() == null) {
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_METHOD_FORBBIDEN);
			return;
		}
		// 如果为POST,那么只能POST,如果是Get,那么都可以
		if (message.getMethod() == QueryMethod.POST && msg.method() != POST) {
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_METHOD_ERROR);
			return;
		}
		@SuppressWarnings("unchecked")
		HttpHandler handler = (HttpHandler) facotry.getHandler(url);
		if (handler == null) {
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_URL_FORBBIDEN);
			return;
		}
		message.setChannel(ctx.channel());
		// 如果是POST,最后再来解析参数
		if (msg.method() == POST) {
			try {
				HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(msg);
				List postData = postDecoder.getBodyHttpDatas();
				for (InterfaceHttpData data : postData) {
					if (data instanceof Attribute) {
						Attribute at = (Attribute) data;
						String name = at.getName();
						String value = at.getValue();
						params.putObj(name, value);
					} else if (data instanceof FileUpload) {
						FileUpload fileUpload = (FileUpload) data;
						int readableBytes = fileUpload.content().readableBytes();
						// 没内容的文件GG掉
						if (readableBytes > 0) {
							String name = fileUpload.getFilename();
							byte[] file = new byte[readableBytes];
							fileUpload.content().readBytes(file);
							message.getFiles().put(name, file);
						}
					}
				}
			} catch (Exception e) {
				log.error(e.getMessage(), e);
				HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_POST_ERROR);
				return;
			}
		}
		message.putAll(params);
		try {
			message.decode();
		} catch (Exception e) {
			HttpUtil.sendResponseError(ctx.channel(), RequestErrorCode.ERROR_MESSAGE_PARSE, e.getMessage());
			return;
		}
		message.setKeepAlive(io.netty.handler.codec.http.HttpUtil.isKeepAlive(msg));
		if (dispatchMessage != null) {
			try {
				dispatchMessage.run(message, handler);
			} catch (Exception e) {
				log.error(ctx.channel() + " cause:", e);
				Procs.invoke(onExceptionCaught, channel(), e);
			}
		} else {
			log.warn(serverName + " no dispatch message listener!");
		}
	}

	@Override
	public void startServer() {
		bind(port, onServerBind);
	}

	@Override
	public void stopServer() {
		unbind();
	}

	public String getServerName() {
		return this.serverName;
	}

	public int getPort() {
		return port;
	}

	public int getHttpObjectAggregatorMax() {
		return httpObjectAggregatorMax;
	}

	public Set getWhiteList() {
		return whiteList;
	}

	public UrlMessageFactory getFacotry() {
		return facotry;
	}

	public Proc1 getOnServerBind() {
		return onServerBind;
	}

	public static class HttpServerBuilder {
		private String serverName;
		private int port;
		private int httpObjectAggregatorMax;
		private UrlMessageFactory facotry;
		private Set whiteList;
		// listener
		private Proc1 onServerBind;
		private Proc2 onChannelStateChanged;
		private Proc2> dispatchMessage;
		private Proc2 onMessageOverSize;
		private Proc2 onExceptionCaught;

		public HttpServerBuilder() {
			this.serverName = "Http-Server";
			this.port = 8080;
			this.httpObjectAggregatorMax = 1024 * 1024;
			this.whiteList = new HashSet<>();
			this.dispatchMessage = (message, handler) -> {
				handler.doServer(message);
			};
		}

		public HttpServer build() {
			return new HttpServer(this);
		}

		public HttpServerBuilder serverName(String serverName) {
			this.serverName = serverName;
			return this;
		}

		public HttpServerBuilder port(int port) {
			if (port >= 1024) {
				this.port = port;
			}
			return this;
		}

		public HttpServerBuilder httpObjectAggregatorMax(int httpObjectAggregatorMax) {
			this.httpObjectAggregatorMax = Math.max(512, httpObjectAggregatorMax);
			return this;
		}

		public HttpServerBuilder whiteList(String... remoteAddress) {
			for (String ip : remoteAddress) {
				if (StringUtil.isIp4(ip)) {
					this.whiteList.add(ip);
				}
			}
			return this;
		}

		public HttpServerBuilder factory(UrlMessageFactory factory) {
			this.facotry = factory;
			return this;
		}

		public HttpServerBuilder onServerBind(Proc1 onServerBind) {
			this.onServerBind = onServerBind;
			return this;
		}

		public HttpServerBuilder onChannelStateChanged(Proc2 onChannelStateChanged) {
			this.onChannelStateChanged = onChannelStateChanged;
			return this;
		}

		public HttpServerBuilder dispatchMessage(Proc2> dispatchMessage) {
			this.dispatchMessage = dispatchMessage;
			return this;
		}

		public HttpServerBuilder onMessageOverSize(Proc2 onMessageOverSize) {
			this.onMessageOverSize = onMessageOverSize;
			return this;
		}

		public HttpServerBuilder onExceptionCaught(Proc2 onExceptionCaught) {
			this.onExceptionCaught = onExceptionCaught;
			return this;
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy