/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you 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.jboss.netty.example.http.upload; import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelHandlerContext; import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.Channels; 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.HttpHeaders; 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.HttpVersion; import org.jboss.netty.handler.codec.http.QueryStringDecoder; import org.jboss.netty.handler.codec.http.multipart.Attribute; import org.jboss.netty.handler.codec.http.multipart.DefaultHttpDataFactory; import org.jboss.netty.handler.codec.http.multipart.DiskAttribute; import org.jboss.netty.handler.codec.http.multipart.DiskFileUpload; import org.jboss.netty.handler.codec.http.multipart.FileUpload; import org.jboss.netty.handler.codec.http.multipart.HttpDataFactory; import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder; import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.EndOfDataDecoderException; import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.ErrorDataDecoderException; import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.IncompatibleDataDecoderException; import org.jboss.netty.handler.codec.http.multipart.HttpPostRequestDecoder.NotEnoughDataDecoderException; import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData; import org.jboss.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType; import org.jboss.netty.logging.InternalLogger; import org.jboss.netty.logging.InternalLoggerFactory; import org.jboss.netty.util.CharsetUtil; import java.io.IOException; import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; public class HttpUploadServerHandler extends SimpleChannelUpstreamHandler { private static final InternalLogger logger = InternalLoggerFactory.getInstance(HttpUploadServerHandler.class); private HttpRequest request; private boolean readingChunks; private final StringBuilder responseContent = new StringBuilder(); private static final HttpDataFactory factory = new DefaultHttpDataFactory( DefaultHttpDataFactory.MINSIZE); // Disk if size exceed MINSIZE private HttpPostRequestDecoder decoder; static { DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file // on exit (in normal // exit) DiskFileUpload.baseDirectory = null; // system temp directory DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on // exit (in normal exit) DiskAttribute.baseDirectory = null; // system temp directory } @Override public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception { if (decoder != null) { decoder.cleanFiles(); } } @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { if (!readingChunks) { // clean previous FileUpload if Any if (decoder != null) { decoder.cleanFiles(); decoder = null; } HttpRequest request = this.request = (HttpRequest) e.getMessage(); URI uri = new URI(request.getUri()); if (!uri.getPath().startsWith("/form")) { // Write Menu writeMenu(e); return; } responseContent.setLength(0); responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n"); responseContent.append("===================================\r\n"); responseContent.append("VERSION: " + request.getProtocolVersion().getText() + "\r\n"); responseContent.append("REQUEST_URI: " + request.getUri() + "\r\n\r\n"); responseContent.append("\r\n\r\n"); // new method List<Entry<String, String>> headers = request.getHeaders(); for (Entry<String, String> entry: headers) { responseContent.append("HEADER: " + entry.getKey() + '=' + entry.getValue() + "\r\n"); } responseContent.append("\r\n\r\n"); // new method Set<Cookie> cookies; String value = request.getHeader(HttpHeaders.Names.COOKIE); if (value == null) { cookies = Collections.emptySet(); } else { CookieDecoder decoder = new CookieDecoder(); cookies = decoder.decode(value); } for (Cookie cookie: cookies) { responseContent.append("COOKIE: " + cookie.toString() + "\r\n"); } responseContent.append("\r\n\r\n"); QueryStringDecoder decoderQuery = new QueryStringDecoder(request .getUri()); Map<String, List<String>> uriAttributes = decoderQuery .getParameters(); for (Entry<String, List<String>> attr: uriAttributes.entrySet()) { for (String attrVal: attr.getValue()) { responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n"); } } responseContent.append("\r\n\r\n"); // if GET Method: should not try to create a HttpPostRequestDecoder try { decoder = new HttpPostRequestDecoder(factory, request); } catch (ErrorDataDecoderException e1) { e1.printStackTrace(); responseContent.append(e1.getMessage()); writeResponse(e.getChannel()); Channels.close(e.getChannel()); return; } catch (IncompatibleDataDecoderException e1) { // GET Method: should not try to create a HttpPostRequestDecoder // So OK but stop here responseContent.append(e1.getMessage()); responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n"); writeResponse(e.getChannel()); return; } responseContent.append("Is Chunked: " + request.isChunked() + "\r\n"); responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n"); if (request.isChunked()) { // Chunk version responseContent.append("Chunks: "); readingChunks = true; } else { // Not chunk version readHttpDataAllReceive(e.getChannel()); responseContent .append("\r\n\r\nEND OF NOT CHUNKED CONTENT\r\n"); writeResponse(e.getChannel()); } } else { // New chunk is received HttpChunk chunk = (HttpChunk) e.getMessage(); try { decoder.offer(chunk); } catch (ErrorDataDecoderException e1) { e1.printStackTrace(); responseContent.append(e1.getMessage()); writeResponse(e.getChannel()); Channels.close(e.getChannel()); return; } responseContent.append('o'); // example of reading chunk by chunk (minimize memory usage due to Factory) readHttpDataChunkByChunk(); // example of reading only if at the end if (chunk.isLast()) { readHttpDataAllReceive(e.getChannel()); writeResponse(e.getChannel()); readingChunks = false; } } } /** * Example of reading all InterfaceHttpData from finished transfer */ private void readHttpDataAllReceive(Channel channel) { List<InterfaceHttpData> datas; try { datas = decoder.getBodyHttpDatas(); } catch (NotEnoughDataDecoderException e1) { // Should not be! e1.printStackTrace(); responseContent.append(e1.getMessage()); writeResponse(channel); Channels.close(channel); return; } for (InterfaceHttpData data: datas) { writeHttpData(data); } responseContent.append("\r\n\r\nEND OF CONTENT AT FINAL END\r\n"); } /** * Example of reading request by chunk and getting values from chunk to * chunk */ private void readHttpDataChunkByChunk() { try { while (decoder.hasNext()) { InterfaceHttpData data = decoder.next(); if (data != null) { // new value writeHttpData(data); } } } catch (EndOfDataDecoderException e1) { // end responseContent .append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n"); } } private void writeHttpData(InterfaceHttpData data) { if (data.getHttpDataType() == HttpDataType.Attribute) { Attribute attribute = (Attribute) data; String value; try { value = attribute.getValue(); } catch (IOException e1) { // Error while reading data from File, only print name and error e1.printStackTrace(); responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + attribute.getName() + " Error while reading value: " + e1.getMessage() + "\r\n"); return; } if (value.length() > 100) { responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + attribute.getName() + " data too long\r\n"); } else { responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + attribute.toString() + "\r\n"); } } else { responseContent.append("\r\nBODY FileUpload: " + data.getHttpDataType().name() + ": " + data.toString() + "\r\n"); if (data.getHttpDataType() == HttpDataType.FileUpload) { FileUpload fileUpload = (FileUpload) data; if (fileUpload.isCompleted()) { if (fileUpload.length() < 10000) { responseContent.append("\tContent of file\r\n"); try { responseContent.append(fileUpload.getString(fileUpload.getCharset())); } catch (IOException e1) { // do nothing for the example e1.printStackTrace(); } responseContent.append("\r\n"); } else { responseContent .append("\tFile too long to be printed out:" + fileUpload.length() + "\r\n"); } // fileUpload.isInMemory();// tells if the file is in Memory // or on File // fileUpload.renameTo(dest); // enable to move into another // File dest // decoder.removeFileUploadFromClean(fileUpload); //remove // the File of to delete file } else { responseContent .append("\tFile to be continued but should not!\r\n"); } } } } private void writeResponse(Channel channel) { // Convert the response content to a ChannelBuffer. ChannelBuffer buf = ChannelBuffers.copiedBuffer(responseContent .toString(), CharsetUtil.UTF_8); responseContent.setLength(0); // Decide whether to close the connection or not. boolean close = HttpHeaders.Values.CLOSE.equalsIgnoreCase(request .getHeader(HttpHeaders.Names.CONNECTION)) || request.getProtocolVersion().equals(HttpVersion.HTTP_1_0) && !HttpHeaders.Values.KEEP_ALIVE.equalsIgnoreCase(request .getHeader(HttpHeaders.Names.CONNECTION)); // Build the response object. HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); response.setContent(buf); response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8"); if (!close) { // There's no need to add 'Content-Length' header // if this is the last response. response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String .valueOf(buf.readableBytes())); } Set<Cookie> cookies; String value = request.getHeader(HttpHeaders.Names.COOKIE); if (value == null) { cookies = Collections.emptySet(); } else { CookieDecoder decoder = new CookieDecoder(); cookies = decoder.decode(value); } if (!cookies.isEmpty()) { // Reset the cookies if necessary. CookieEncoder cookieEncoder = new CookieEncoder(true); for (Cookie cookie: cookies) { cookieEncoder.addCookie(cookie); response.addHeader(HttpHeaders.Names.SET_COOKIE, cookieEncoder .encode()); cookieEncoder = new CookieEncoder(true); } } // Write the response. ChannelFuture future = channel.write(response); // Close the connection after the write operation is done if necessary. if (close) { future.addListener(ChannelFutureListener.CLOSE); } } private void writeMenu(MessageEvent e) { // print several HTML forms // Convert the response content to a ChannelBuffer. responseContent.setLength(0); // create Pseudo Menu responseContent.append("<html>"); responseContent.append("<head>"); responseContent.append("<title>Netty Test Form</title>\r\n"); responseContent.append("</head>\r\n"); responseContent .append("<body bgcolor=white><style>td{font-size: 12pt;}</style>"); responseContent.append("<table border=\"0\">"); responseContent.append("<tr>"); responseContent.append("<td>"); responseContent.append("<h1>Netty Test Form</h1>"); responseContent.append("Choose one FORM"); responseContent.append("</td>"); responseContent.append("</tr>"); responseContent.append("</table>\r\n"); // GET responseContent .append("<CENTER>GET FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); responseContent.append("<FORM ACTION=\"/formget\" METHOD=\"GET\">"); responseContent .append("<input type=hidden name=getform value=\"GET\">"); responseContent.append("<table border=\"0\">"); responseContent .append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>"); responseContent .append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>"); responseContent .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>"); responseContent.append("</td></tr>"); responseContent .append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>"); responseContent .append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>"); responseContent.append("</table></FORM>\r\n"); responseContent .append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); // POST responseContent .append("<CENTER>POST FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); responseContent.append("<FORM ACTION=\"/formpost\" METHOD=\"POST\">"); responseContent .append("<input type=hidden name=getform value=\"POST\">"); responseContent.append("<table border=\"0\">"); responseContent .append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>"); responseContent .append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>"); responseContent .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>"); responseContent .append("<tr><td>Fill with file (only file name will be transmitted): <br> " + "<input type=file name=\"myfile\">"); responseContent.append("</td></tr>"); responseContent .append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>"); responseContent .append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>"); responseContent.append("</table></FORM>\r\n"); responseContent .append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); // POST with enctype="multipart/form-data" responseContent .append("<CENTER>POST MULTIPART FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); responseContent .append("<FORM ACTION=\"/formpostmultipart\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">"); responseContent .append("<input type=hidden name=getform value=\"POST\">"); responseContent.append("<table border=\"0\">"); responseContent .append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>"); responseContent .append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>"); responseContent .append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>"); responseContent .append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">"); responseContent.append("</td></tr>"); responseContent .append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>"); responseContent .append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>"); responseContent.append("</table></FORM>\r\n"); responseContent .append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); responseContent.append("</body>"); responseContent.append("</html>"); ChannelBuffer buf = ChannelBuffers.copiedBuffer(responseContent .toString(), CharsetUtil.UTF_8); // Build the response object. HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); response.setContent(buf); response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8"); response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, String.valueOf(buf .readableBytes())); // Write the response. e.getChannel().write(response); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { logger.error(responseContent.toString(), e.getCause()); e.getChannel().close(); } }