Skip to content

Commit

Permalink
Merge pull request #259 from fintechworks/feature/add_file_upload_to_…
Browse files Browse the repository at this point in the history
…qrest

qrest: Add participant to extract file from POST
  • Loading branch information
ar authored Aug 11, 2022
2 parents 11b0a18 + 10b0131 commit b9ce14a
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 4 deletions.
5 changes: 5 additions & 0 deletions modules/qrest/src/dist/deploy/30_qrest_txnmgr.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<participant class="org.jpos.qrest.participant.Router">
<route path="/q2**" method="GET" name="q2"/>
<route path="/q2**" method="POST" name="q2"/>
<route path="/test/load_file" method="POST" name="upload_file"/>
<route path="/welcome.html" method="GET" name="welcome" />
<route path="/dynamic" method="GET" name="dynamic" />
<route path="/" method="GET" name="index" />
Expand All @@ -22,6 +23,10 @@
<property name="content" value="welcome.html" />
</participant>
</group>
<group name="upload_file">
<participant class="org.jpos.qrest.ExtractFile" />
<participant class="org.jpos.qrest.test.participant.DumpFile" />
</group>
<group name="index">
<participant class="org.jpos.qrest.participant.StaticContent">
<property name="documentRoot" value="html" />
Expand Down
106 changes: 106 additions & 0 deletions modules/qrest/src/main/java/org/jpos/qrest/ExtractFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2021 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.jpos.qrest;

import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.InterfaceHttpData.HttpDataType;
import org.jpos.core.Configurable;
import org.jpos.core.Configuration;
import org.jpos.core.ConfigurationException;
import org.jpos.transaction.Context;
import org.jpos.transaction.TransactionParticipant;

import java.io.*;
import java.nio.channels.FileChannel;

import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static org.jpos.qrest.Constants.REQUEST;
import static org.jpos.qrest.Constants.RESPONSE;

/**
* Extracts a file from the request
*/
public class ExtractFile implements TransactionParticipant, Configurable {

private String ctxKey;

@Override
public int prepare(long id, Serializable context) {
Context ctx = (Context) context;
FullHttpRequest request = ctx.get(REQUEST);

try {
ctx.put(ctxKey, getFileFromRequest(request));
} catch (IOException e) {
ctx.log(e);
ctx.put(RESPONSE, new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND));
return ABORTED;
}

return PREPARED | READONLY | NO_JOIN;
}

protected File getFileFromRequest(FullHttpRequest httpRequest) throws IOException {

HttpPostRequestDecoder httpDecoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(true), httpRequest);
httpDecoder.setDiscardThreshold(0);

httpDecoder.offer(httpRequest);
return readChunk(httpDecoder);

}

private File readChunk(HttpPostRequestDecoder httpDecoder) throws IOException {

for (InterfaceHttpData data : httpDecoder.getBodyHttpDatas()) {
if (data == null) continue;
try {
HttpDataType httpDataType = data.getHttpDataType();
if (httpDataType == HttpDataType.FileUpload) {
final FileUpload fileUpload = (FileUpload) data;
final File file = File.createTempFile(fileUpload.getFilename(), ".qrest.extract_file");
if (!file.exists() && !file.createNewFile()) {
throw new IOException("Can't create file " + file.getAbsolutePath());
}
try (FileInputStream fis = new FileInputStream(fileUpload.getFile());
FileChannel inputChannel = fis.getChannel();
FileOutputStream fos = new FileOutputStream(file);
FileChannel outputChannel = fos.getChannel()) {
outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
}
return file;
}
} finally {
data.release();
}
}
return null;
}

@Override
public void setConfiguration(Configuration cfg) throws ConfigurationException {
this.ctxKey = cfg.get("CTX_KEY", "FILE_FROM_REQUEST");
}
}
27 changes: 23 additions & 4 deletions modules/qrest/src/test/java/org/jpos/qrest/RestTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,23 @@

import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import org.apache.http.entity.ContentType;
import org.jpos.q2.Q2;
import org.jpos.util.NameRegistrar;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
import java.io.File;
import java.nio.file.Files;

import static org.hamcrest.Matchers.*;
import static io.netty.handler.codec.http.HttpHeaderValues.APPLICATION_JSON;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

public class RestTest {
private static final String BASE_URL = "http://localhost:8081/";
private static Q2 q2;

@BeforeAll
public static void setUp() throws NameRegistrar.NotFoundException {
RestAssured.baseURI = BASE_URL;
Expand Down Expand Up @@ -100,4 +103,20 @@ public void testMultiplesTMs() {
.body("name", equalTo("txnmgr2")
);
}

@Test
void testUploadFile() throws Exception {

File tempFile = File.createTempFile("qrest", "test");
Files.write(tempFile.toPath(), "hello".getBytes());

given().log().all()
.contentType(ContentType.MULTIPART_FORM_DATA.getMimeType())
.multiPart("file", tempFile, "multipart/form-data")
.post("/test/load_file")
.then()
.statusCode(200)
.assertThat()
.body("content", equalTo("hello"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.jpos.qrest.test.participant;

import io.netty.handler.codec.http.HttpResponseStatus;
import org.jpos.qrest.Response;
import org.jpos.transaction.Context;
import org.jpos.transaction.TransactionParticipant;

import java.io.File;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.Collections;

import static org.jpos.qrest.Constants.RESPONSE;

/**
* @author Arturo Volpe
* @since 2022-08-03
*/
public class DumpFile implements TransactionParticipant {


@Override
public int prepare(long id, Serializable context) {

Context ctx = (Context) context;

File file = ctx.get("FILE_FROM_REQUEST");
try {
String content = String.join("", Files.readAllLines(file.toPath()));
ctx.put(RESPONSE, new Response(HttpResponseStatus.OK, Collections.singletonMap("content", content)));
return PREPARED | NO_JOIN | READONLY;
} catch (Exception e) {
ctx.log(e);
ctx.put(RESPONSE, new Response(HttpResponseStatus.INTERNAL_SERVER_ERROR, Collections.emptyMap()));
return ABORTED;
}
}

}

0 comments on commit b9ce14a

Please # to comment.