用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
全站熱搜
留言列表