dpfmanager.shell.modules.server.post.HttpPostHandler Maven / Gradle / Ivy
/**
* HttpPostHandler.java
This program is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version; or, at your
* choice, under the terms of the Mozilla Public License, v. 2.0. SPDX GPL-3.0+ or MPL-2.0+.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the Mozilla Public License for more details.
You should
* have received a copy of the GNU General Public License and the Mozilla Public License along with
* this program. If not, see http://www.gnu.org/licenses/
* and at http://mozilla.org/MPL/2.0 .
NB: for the
* © statement, include Easy Innova SL or other company/Person contributing the code.
©
* 2015 Easy Innova, SL
*
* @author Adrià Llorens
* @version 1.0
* @since 23/7/2015
*/
package dpfmanager.shell.modules.server.post;
import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import dpfmanager.shell.core.DPFManagerProperties;
import dpfmanager.shell.core.config.BasicConfig;
import dpfmanager.shell.core.context.DpfContext;
import dpfmanager.shell.modules.messages.messages.ExceptionMessage;
import dpfmanager.shell.modules.messages.messages.LogMessage;
import dpfmanager.shell.modules.server.messages.PostMessage;
import dpfmanager.shell.modules.server.messages.StatusMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.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.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.ErrorDataDecoderException;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import io.netty.util.CharsetUtil;
import com.google.gson.Gson;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.net.util.Base64;
import org.apache.logging.log4j.Level;
import org.apache.tools.zip.ZipEntry;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.zip.ZipOutputStream;
public class HttpPostHandler extends SimpleChannelInboundHandler {
private final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE);
private DpfContext context;
private ResourceBundle bundle;
private HttpRequest request;
private HttpData partialContent;
private final StringBuilder responseContent = new StringBuilder();
private HttpPostRequestDecoder decoder;
// Request params
private Long uuid;
private Long id;
private String filepath;
private String configpath;
private File destFolder;
static {
// should delete file on exit (in normal exit)
DiskAttribute.deleteOnExitTemporaryFile = true;
DiskFileUpload.deleteOnExitTemporaryFile = true;
// system temp directory
DiskFileUpload.baseDirectory = null;
DiskAttribute.baseDirectory = null;
}
public HttpPostHandler(DpfContext context) {
this.context = context;
bundle = DPFManagerProperties.getBundle();
}
@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;
if (request.method().equals(HttpMethod.POST) || request.method().equals(HttpMethod.OPTIONS)) {
// Start new POST request
init();
decoder = new HttpPostRequestDecoder(factory, request);
} else {
sendError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED);
return;
}
}
// POST request
if (decoder != null) {
if (msg instanceof HttpContent) {
// New chunk is received
try {
HttpContent chunk = (HttpContent) msg;
decoder.offer(chunk);
readHttpDataChunkByChunk();
if (chunk instanceof LastHttpContent) {
tractReadPost(ctx);
reset();
return;
}
} catch (ErrorDataDecoderException e1) {
e1.printStackTrace();
ctx.channel().close();
return;
}
}
}
}
private void init() {
// Init new request
uuid = System.currentTimeMillis();
id = null;
filepath = null;
configpath = null;
}
private void reset() {
// End of request, destroy decoder
decoder.destroy();
decoder = null;
request = null;
}
private void tractReadPost(ChannelHandlerContext ctx) {
if (id != null) {
askForJob(ctx);
} else {
newFileCheck(ctx);
}
}
/**
* Main methods
*/
private void askForJob(ChannelHandlerContext ctx) {
StatusMessage sm = (StatusMessage) context.sendAndWaitResponse(BasicConfig.MODULE_DATABASE, new PostMessage(PostMessage.Type.ASK, id));
Map map = new HashMap<>();
map.put("type", "ASK");
map.put("id", id.toString());
if (sm.isRunning()) {
map.put("status", "Running");
map.put("processed", sm.getProcessed().toString());
map.put("total", sm.getTotal().toString());
} else if (sm.isFinished()) {
// Zip folder
String path = zipFolder(sm.getFolder());
String link = parsePathToLink(path);
map.put("status", "Finished");
map.put("path", link);
printOut("Finished check, sending to user...");
printOut(" Uuid: " + id.toString());
printOut(" Path: " + path);
} else {
map.put("status", "NotFound");
}
responseContent.append(new Gson().toJson(map));
writeResponse(ctx.channel());
}
private void newFileCheck(ChannelHandlerContext ctx) {
Map map = new HashMap<>();
map.put("type", "CHECK");
map.put("id", uuid.toString());
map.put("input", getName(filepath));
if (configpath == null) {
// Error miss config file
map.put("myerror", bundle.getString("missingConfig"));
} else if (filepath == null) {
// Error miss file to check
map.put("myerror", bundle.getString("missingFile"));
}
responseContent.append(new Gson().toJson(map));
writeResponse(ctx.channel());
// OK start the check
if (!map.containsKey("myerror")) {
context.send(BasicConfig.MODULE_SERVER, new PostMessage(PostMessage.Type.POST, uuid, filepath, configpath));
printOut("");
printOut("New file check received.");
printOut(" Uuid: " + uuid);
} else {
deleteTmpFolder(uuid);
}
}
private void deleteTmpFolder(Long uuid) {
try {
File folder = new File(DPFManagerProperties.getServerDir() + "/" + uuid);
if (folder.exists() && folder.isDirectory()) {
FileUtils.deleteDirectory(folder);
}
} catch (Exception e) {
context.send(BasicConfig.MODULE_MESSAGE, new ExceptionMessage("Exception in remove server folder", e));
}
}
private String getName(String path) {
String ret = "";
if (path != null) {
if (path.contains(";")) {
// List files
String[] paths = path.split(";");
for (String newPath : paths) {
ret = ret + newPath.substring(newPath.lastIndexOf("\\") + 1, newPath.length()) + ", ";
}
ret = ret.substring(0, ret.length() - 1);
} else {
ret = path.substring(path.lastIndexOf("\\") + 1, path.length());
}
}
return ret;
}
/**
* Zip functoins
*/
public String parsePathToLink(String path) {
String[] splited = path.split("/");
String last = splited[splited.length - 1];
String last2 = splited[splited.length - 2];
return "/" + last2 + "/" + last;
}
public String zipFolder(String folder) {
// Zip path
String outputFile = folder + ".zip";
if (folder.endsWith("/")) {
outputFile = folder.substring(0, folder.length() - 1) + ".zip";
}
// Check if exists
if (new File(outputFile).exists()) {
return outputFile;
}
// Make the zip
try {
ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(outputFile));
compressDirectoryToZipfile(folder, folder, zipFile);
IOUtils.closeQuietly(zipFile);
return outputFile;
} catch (Exception e) {
return null;
}
}
private void compressDirectoryToZipfile(String rootDir, String sourceDir, ZipOutputStream out) throws IOException, FileNotFoundException {
for (File file : new File(sourceDir).listFiles()) {
if (file.isDirectory()) {
compressDirectoryToZipfile(rootDir, sourceDir + file.getName() + "/", out);
} else {
ZipEntry entry = new ZipEntry(sourceDir.replace(rootDir, "") + file.getName());
out.putNextEntry(entry);
FileInputStream in = new FileInputStream(sourceDir + file.getName());
IOUtils.copy(in, out);
IOUtils.closeQuietly(in);
}
}
}
/**
* 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) {
if (partialContent == data) {
partialContent = null;
}
try {
if (data.getHttpDataType() == HttpDataType.Attribute) {
parseAttributeData((Attribute) data);
} else if (data.getHttpDataType() == HttpDataType.FileUpload) {
parseFileUploadData((FileUpload) data);
} else {
}
} catch (Exception e) {
e.printStackTrace();
return;
} finally {
data.release();
}
}
}
} catch (HttpPostRequestDecoder.EndOfDataDecoderException e1) {
// End
}
}
private void parseAttributeData(Attribute attribute) throws IOException {
String name = attribute.getName();
if (name.equals("id")) {
id = Long.parseLong(attribute.getValue());
} else if (name.equals("config")) {
destFolder = createNewDirectory(uuid);
String encoded = attribute.getValue();
String decoded = new String(Base64.decodeBase64(encoded), "UTF-8");
File dest = new File(destFolder.getAbsolutePath() + "/config.dpf");
configpath = dest.getAbsolutePath();
FileUtils.writeStringToFile(dest, decoded);
}
}
private void parseFileUploadData(FileUpload fileUpload) throws IOException {
if (fileUpload.isCompleted()) {
destFolder = createNewDirectory(uuid);
if (fileUpload.getName().equals("config")) {
// Save config file
File dest = new File(destFolder.getAbsolutePath() + "/config.dpf");
configpath = dest.getAbsolutePath();
fileUpload.renameTo(dest);
} else {
// Save file to check
File dest = new File(destFolder.getAbsolutePath() + "/" + fileUpload.getFilename());
if (filepath == null) {
filepath = dest.getAbsolutePath();
} else {
filepath = filepath + ";" + dest.getAbsolutePath();
}
fileUpload.renameTo(dest);
}
}
}
private File createNewDirectory(Long uuid) {
String serverDir = DPFManagerProperties.getServerDir();
File folder = new File(serverDir + "/" + uuid);
if (folder.exists()) {
return folder;
}
folder.mkdirs();
return folder;
}
/**
* Util functions
*/
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) {
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
}
// Extra headers
response.headers().set(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
// 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 sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
private void printOut(String message) {
context.send(BasicConfig.MODULE_MESSAGE, new LogMessage(getClass(), Level.DEBUG, message));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}