/*
* 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 io.netty.example.http.upload;
import io.netty.buffer.
ByteBuf;
import io.netty.channel.
Channel;
import io.netty.channel.
ChannelFuture;
import io.netty.channel.
ChannelFutureListener;
import io.netty.channel.
ChannelHandlerContext;
import io.netty.channel.
SimpleChannelInboundHandler;
import io.netty.handler.codec.http.
DefaultFullHttpResponse;
import io.netty.handler.codec.http.
FullHttpResponse;
import io.netty.handler.codec.http.
HttpContent;
import io.netty.handler.codec.http.
HttpHeaderNames;
import io.netty.handler.codec.http.
HttpUtil;
import io.netty.handler.codec.http.
HttpHeaderValues;
import io.netty.handler.codec.http.
HttpMethod;
import io.netty.handler.codec.http.
HttpObject;
import io.netty.handler.codec.http.
HttpRequest;
import io.netty.handler.codec.http.
HttpResponseStatus;
import io.netty.handler.codec.http.
HttpVersion;
import io.netty.handler.codec.http.
LastHttpContent;
import io.netty.handler.codec.http.
QueryStringDecoder;
import io.netty.handler.codec.http.cookie.
Cookie;
import io.netty.handler.codec.http.cookie.
ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.
ServerCookieEncoder;
import io.netty.handler.codec.http.multipart.
Attribute;
import io.netty.handler.codec.http.multipart.
DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.
DiskAttribute;
import io.netty.handler.codec.http.multipart.
DiskFileUpload;
import io.netty.handler.codec.http.multipart.
FileUpload;
import io.netty.handler.codec.http.multipart.
HttpData;
import io.netty.handler.codec.http.multipart.
HttpDataFactory;
import io.netty.handler.codec.http.multipart.
HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.
HttpPostRequestDecoder.
EndOfDataDecoderException;
import io.netty.handler.codec.http.multipart.
HttpPostRequestDecoder.
ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.
InterfaceHttpData;
import io.netty.handler.codec.http.multipart.
InterfaceHttpData.
HttpDataType;
import io.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;
import java.util.logging.
Level;
import java.util.logging.
Logger;
import static io.netty.buffer.
Unpooled.*;
public class
HttpUploadServerHandler extends
SimpleChannelInboundHandler<
HttpObject> {
private static final
Logger logger =
Logger.
getLogger(
HttpUploadServerHandler.class.
getName());
private
HttpRequest request;
private boolean
readingChunks;
private
HttpData partialContent;
private final
StringBuilder responseContent = new
StringBuilder();
private static final
HttpDataFactory factory =
new
DefaultHttpDataFactory(
DefaultHttpDataFactory.
MINSIZE); // Disk if size exceed
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
channelInactive(
ChannelHandlerContext ctx) throws
Exception {
if (
decoder != null) {
decoder.
cleanFiles();
}
}
@
Override
public void
channelRead0(
ChannelHandlerContext ctx,
HttpObject msg) throws
Exception {
if (
msg instanceof
HttpRequest) {
HttpRequest request = this.
request = (
HttpRequest)
msg;
URI uri = new
URI(
request.
uri());
if (!
uri.
getPath().
startsWith("/form")) {
// Write Menu
writeMenu(
ctx);
return;
}
responseContent.
setLength(0);
responseContent.
append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
responseContent.
append("===================================\r\n");
responseContent.
append("VERSION: " +
request.
protocolVersion().
text() + "\r\n");
responseContent.
append("REQUEST_URI: " +
request.
uri() + "\r\n\r\n");
responseContent.
append("\r\n\r\n");
// new getMethod
for (
Entry<
String,
String>
entry :
request.
headers()) {
responseContent.
append("HEADER: " +
entry.
getKey() + '=' +
entry.
getValue() + "\r\n");
}
responseContent.
append("\r\n\r\n");
// new getMethod
Set<
Cookie>
cookies;
String value =
request.
headers().
get(
HttpHeaderNames.
COOKIE);
if (
value == null) {
cookies =
Collections.
emptySet();
} else {
cookies =
ServerCookieDecoder.
STRICT.
decode(
value);
}
for (
Cookie cookie :
cookies) {
responseContent.
append("COOKIE: " +
cookie + "\r\n");
}
responseContent.
append("\r\n\r\n");
QueryStringDecoder decoderQuery = new
QueryStringDecoder(
request.
uri());
Map<
String,
List<
String>>
uriAttributes =
decoderQuery.
parameters();
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
if (
request.
method().
equals(
HttpMethod.
GET)) {
// GET Method: should not try to create a HttpPostRequestDecoder
// So stop here
responseContent.
append("\r\n\r\nEND OF GET CONTENT\r\n");
// Not now: LastHttpContent will be sent writeResponse(ctx.channel());
return;
}
try {
decoder = new
HttpPostRequestDecoder(
factory,
request);
} catch (
ErrorDataDecoderException e1) {
e1.
printStackTrace();
responseContent.
append(
e1.
getMessage());
writeResponse(
ctx.
channel());
ctx.
channel().
close();
return;
}
readingChunks =
HttpUtil.
isTransferEncodingChunked(
request);
responseContent.
append("Is Chunked: " +
readingChunks + "\r\n");
responseContent.
append("IsMultipart: " +
decoder.
isMultipart() + "\r\n");
if (
readingChunks) {
// Chunk version
responseContent.
append("Chunks: ");
readingChunks = true;
}
}
// check if the decoder was constructed before
// if not it handles the form get
if (
decoder != null) {
if (
msg instanceof
HttpContent) {
// New chunk is received
HttpContent chunk = (
HttpContent)
msg;
try {
decoder.
offer(
chunk);
} catch (
ErrorDataDecoderException e1) {
e1.
printStackTrace();
responseContent.
append(
e1.
getMessage());
writeResponse(
ctx.
channel());
ctx.
channel().
close();
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 instanceof
LastHttpContent) {
writeResponse(
ctx.
channel());
readingChunks = false;
reset();
}
}
} else {
writeResponse(
ctx.
channel());
}
}
private void
reset() {
request = null;
// destroy the decoder to release all resources
decoder.
destroy();
decoder = null;
}
/**
* 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) {
// check if current HttpData is a FileUpload and previously set as partial
if (
partialContent ==
data) {
logger.
info(" 100% (FinalSize: " +
partialContent.
length() + ")");
partialContent = null;
}
try {
// new value
writeHttpData(
data);
} finally {
data.
release();
}
}
}
// Check partial decoding for a FileUpload
InterfaceHttpData data =
decoder.
currentPartialHttpData();
if (
data != null) {
StringBuilder builder = new
StringBuilder();
if (
partialContent == null) {
partialContent = (
HttpData)
data;
if (
partialContent instanceof
FileUpload) {
builder.
append("Start FileUpload: ")
.
append(((
FileUpload)
partialContent).
getFilename()).
append(" ");
} else {
builder.
append("Start Attribute: ")
.
append(
partialContent.
getName()).
append(" ");
}
builder.
append("(DefinedSize: ").
append(
partialContent.
definedLength()).
append(")");
}
if (
partialContent.
definedLength() > 0) {
builder.
append(" ").
append(
partialContent.
length() * 100 /
partialContent.
definedLength())
.
append("% ");
logger.
info(
builder.
toString());
} else {
builder.
append(" ").
append(
partialContent.
length()).
append(" ");
logger.
info(
builder.
toString());
}
}
} 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 + "\r\n");
}
} else {
responseContent.
append("\r\nBODY FileUpload: " +
data.
getHttpDataType().
name() + ": " +
data
+ "\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.
ByteBuf buf =
copiedBuffer(
responseContent.
toString(),
CharsetUtil.
UTF_8);
responseContent.
setLength(0);
// Decide whether to close the connection or not.
boolean
close =
request.
headers().
contains(
HttpHeaderNames.
CONNECTION,
HttpHeaderValues.
CLOSE, true)
||
request.
protocolVersion().
equals(
HttpVersion.
HTTP_1_0)
&& !
request.
headers().
contains(
HttpHeaderNames.
CONNECTION,
HttpHeaderValues.
KEEP_ALIVE, true);
// Build the response object.
FullHttpResponse response = new
DefaultFullHttpResponse(
HttpVersion.
HTTP_1_1,
HttpResponseStatus.
OK,
buf);
response.
headers().
set(
HttpHeaderNames.
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.
headers().
setInt(
HttpHeaderNames.
CONTENT_LENGTH,
buf.
readableBytes());
}
Set<
Cookie>
cookies;
String value =
request.
headers().
get(
HttpHeaderNames.
COOKIE);
if (
value == null) {
cookies =
Collections.
emptySet();
} else {
cookies =
ServerCookieDecoder.
STRICT.
decode(
value);
}
if (!
cookies.
isEmpty()) {
// Reset the cookies if necessary.
for (
Cookie cookie :
cookies) {
response.
headers().
add(
HttpHeaderNames.
SET_COOKIE,
ServerCookieEncoder.
STRICT.
encode(
cookie));
}
}
// Write the response.
ChannelFuture future =
channel.
writeAndFlush(
response);
// Close the connection after the write operation is done if necessary.
if (
close) {
future.
addListener(
ChannelFutureListener.
CLOSE);
}
}
private void
writeMenu(
ChannelHandlerContext ctx) {
// 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>");
ByteBuf buf =
copiedBuffer(
responseContent.
toString(),
CharsetUtil.
UTF_8);
// Build the response object.
FullHttpResponse response = new
DefaultFullHttpResponse(
HttpVersion.
HTTP_1_1,
HttpResponseStatus.
OK,
buf);
response.
headers().
set(
HttpHeaderNames.
CONTENT_TYPE, "text/html; charset=UTF-8");
response.
headers().
setInt(
HttpHeaderNames.
CONTENT_LENGTH,
buf.
readableBytes());
// Write the response.
ctx.
channel().
writeAndFlush(
response);
}
@
Override
public void
exceptionCaught(
ChannelHandlerContext ctx,
Throwable cause) throws
Exception {
logger.
log(
Level.
WARNING,
responseContent.
toString(),
cause);
ctx.
channel().
close();
}
}