用Netty實現的一個簡單的HTTP服務器,可以處理靜態文件,例子中的注釋也比較全。主要是對HTTP的理解,接下來的文章中我也會更新一些HTTP相關的文章以及對例子的進一步完善,由淺到深,記錄一些我的學習過程!

1.Server

public class HttpServer {
	public static void main(String[] args) {
		ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
				Executors.newCachedThreadPool(), Executors.newCachedThreadPool()));

		bootstrap.setPipelineFactory(new HttpServerPipelineFactory());

		bootstrap.bind(new InetSocketAddress(8080));
		System.out.println("服務器已經启動,請訪問http://127.0.0.1:8080/index.html進行測試!\n\n");
	}
}

 

2.Pipeline

public class HttpServerPipelineFactory implements ChannelPipelineFactory {
	public ChannelPipeline getPipeline() throws Exception {
		// Create a default pipeline implementation.
		ChannelPipeline pipeline = pipeline();

		// Uncomment the following line if you want HTTPS
		// SSLEngine engine =
		// SecureChatSslContextFactory.getServerContext().createSSLEngine();
		// engine.setUseClientMode(false);
		// pipeline.addLast("ssl", new SslHandler(engine));

		pipeline.addLast("decoder", new HttpRequestDecoder());
		// Uncomment the following line if you don't want to handle HttpChunks.
		// pipeline.addLast("aggregator", new HttpChunkAggregator(1048576));
		pipeline.addLast("encoder", new HttpResponseEncoder());
		// Remove the following line if you don't want automatic content
		// compression.
		//pipeline.addLast("aggregator", new HttpChunkAggregator(1048576));
		pipeline.addLast("chunkedWriter", new ChunkedWriteHandler());
		pipeline.addLast("deflater", new HttpContentCompressor());
		pipeline.addLast("handler", new HttpRequestHandler());
		return pipeline;
	}
}

3.handler類 

import static org.jboss.netty.handler.codec.http.HttpHeaders.is100ContinueExpected;

import static org.jboss.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.CONTENT_LENGTH;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.COOKIE;
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.SET_COOKIE;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static org.jboss.netty.handler.codec.http.HttpResponseStatus.OK;
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import java.io.File;
import java.io.RandomAccessFile;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.Cookie;
import org.jboss.netty.handler.codec.http.CookieDecoder;
import org.jboss.netty.handler.codec.http.CookieEncoder;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.QueryStringDecoder;
import org.jboss.netty.handler.stream.ChunkedFile;
import org.jboss.netty.util.CharsetUtil;

public class HttpRequestHandler extends SimpleChannelUpstreamHandler {

	private HttpRequest request;
	private boolean readingChunks;

	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
		if (!readingChunks) {
			HttpRequest request = this.request = (HttpRequest) e.getMessage();
			String uri = request.getUri();
			System.out.println("-----------------------------------------------------------------");
			System.out.println("uri:"+uri);
			System.out.println("-----------------------------------------------------------------");
			/**
			 * 100 Continue
			 * 是這样的一種情況:HTTP客戶端程序有一個實體的主體部分要發送给服務器,但希望在發送之前查看下服務器是否會
			 * 接受這個實體,所以在發送實體之前先發送了一個攜帶100
			 * Continue的Expect請求首部的請求。服務器在收到這样的請求後,應該用 100 Continue或一條錯誤碼來進行響應。
			 */
			if (is100ContinueExpected(request)) {
				send100Continue(e);
			}
			// 解析http頭部
			for (Map.Entry<String, String> h : request.getHeaders()) {
				System.out.println("HEADER: " + h.getKey() + " = " + h.getValue() + "\r\n");
			}
			// 解析請求参數
			QueryStringDecoder queryStringDecoder = new QueryStringDecoder(request.getUri());
			Map<String, List<String>> params = queryStringDecoder.getParameters();
			if (!params.isEmpty()) {
				for (Entry<String, List<String>> p : params.entrySet()) {
					String key = p.getKey();
					List<String> vals = p.getValue();
					for (String val : vals) {
						System.out.println("PARAM: " + key + " = " + val + "\r\n");
					}
				}
			}
			if (request.isChunked()) {
				readingChunks = true;
			} else {
				ChannelBuffer content = request.getContent();
				if (content.readable()) {
					System.out.println(content.toString(CharsetUtil.UTF_8));
				}
				writeResponse(e, uri);
			}
		} else {// 为分塊編碼時
			HttpChunk chunk = (HttpChunk) e.getMessage();
			if (chunk.isLast()) {
				readingChunks = false;
				// END OF CONTENT\r\n"
				HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
				if (!trailer.getHeaderNames().isEmpty()) {
					for (String name : trailer.getHeaderNames()) {
						for (String value : trailer.getHeaders(name)) {
							System.out.println("TRAILING HEADER: " + name + " = " + value + "\r\n");
						}
					}
				}
				writeResponse(e, "/");
			} else {
				System.out.println("CHUNK: " + chunk.getContent().toString(CharsetUtil.UTF_8)
						+ "\r\n");
			}
		}
	}

	private void writeResponse(MessageEvent e, String uri) {
		// 解析Connection首部,判斷是否为持久連接
		boolean keepAlive = isKeepAlive(request);

		// Build the response object.
		HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
		response.setStatus(HttpResponseStatus.OK);
		// 服務端可以通過location首部將客戶端導向某個資源的地址。
		// response.addHeader("Location", uri);
		if (keepAlive) {
			// Add 'Content-Length' header only for a keep-alive connection.
			response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes());
		}
		// 得到客戶端的cookie信息,並再次寫到客戶端
		String cookieString = request.getHeader(COOKIE);
		if (cookieString != null) {
			CookieDecoder cookieDecoder = new CookieDecoder();
			Set<Cookie> cookies = cookieDecoder.decode(cookieString);
			if (!cookies.isEmpty()) {
				CookieEncoder cookieEncoder = new CookieEncoder(true);
				for (Cookie cookie : cookies) {
					cookieEncoder.addCookie(cookie);
				}
				response.addHeader(SET_COOKIE, cookieEncoder.encode());
			}
		}
		final String path = Config.getRealPath(uri);
		File localFile = new File(path);
		// 如果文件隱藏或者不存在
		if (localFile.isHidden() || !localFile.exists()) {
			// 邏輯處理
			return;
		}
		// 如果請求路徑为目錄
		if (localFile.isDirectory()) {
			// 邏輯處理
			return;
		}
		RandomAccessFile raf = null;
		try {
			raf = new RandomAccessFile(localFile, "r");
			long fileLength = raf.length();
			response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(fileLength));
			Channel ch = e.getChannel();
			ch.write(response);
			// 這裏又要重新溫習下http的方法,head方法與get方法類似,但是服務器在響應中只返回首部,不會返回實體的主體部分
			if (!request.getMethod().equals(HttpMethod.HEAD)) {
				ch.write(new ChunkedFile(raf, 0, fileLength, 8192));//8kb
			}
		} catch (Exception e2) {
			e2.printStackTrace();
		} finally {
			if (keepAlive) {
				response.setHeader(CONTENT_LENGTH, response.getContent().readableBytes());
			}
			if (!keepAlive) {
				e.getFuture().addListener(ChannelFutureListener.CLOSE);
			}
		}
	}

	private void send100Continue(MessageEvent e) {
		HttpResponse response = new DefaultHttpResponse(HTTP_1_1, CONTINUE);
		e.getChannel().write(response);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
		e.getCause().printStackTrace();
		e.getChannel().close();
	}
}

4.配置類 

public class Config {

	public static String getRealPath(String uri) {
		StringBuilder sb=new StringBuilder("/home/guolei/workspace/Test/web");
		sb.append(uri);
		if (!uri.endsWith("/")) {
			sb.append('/');
		}
		return sb.toString();
	}
}

 

5.頁面

在項目中新建一個文件夾,名稱为web(可以在配置中配置),在文件夾中放入靜態頁面index.html。

6.启動服務器,測試


From:OSChina

arrow
arrow
    全站熱搜

    戮克 發表在 痞客邦 留言(0) 人氣()