Node.js request upload + Jersey server multipart resource => Gson stackoverflow - file-upload

I have a node.js client uploading a file using multipart form toward a Java based jersey server running on apache tomcat.
This is the node.js upload form:
var filePath = path.join(os.tmpDir(), filename);
var fstream = fs.createWriteStream(filePath);
file.pipe(fstream);
fstream.on('close', function() {
var formData = {
custom_file: {
value: fs.createReadStream(filePath),
options: {
filename: filename
}
}
};
request.post({
url: apiBase + reqUrl,
headers: options.headers,
formData: formData
}, function (err, resp, body) {
if (err) {
res.status(500).send('Error');
} else {
res.status(200).send(body);
}
});
});
This is the jersey server multipart resource implementation:
#POST
#Path("/upload")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response upload(#FormDataParam("custom_file") InputStream inputStream,
#FormDataParam("custom_file") FormDataContentDisposition fileDetail) {
return null;
}
I've also added jersey's multipart feature support in my web.xml:
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>org.glassfish.jersey.media.multipart.MultiPartFeature</param-value>
</init-param>
When I use this flow, my java server return the following exception:
SEVERE: Servlet.service() for servlet [springDispatcher] in context with path [/app] threw exception [org.glassfish.jersey.server.ContainerException: java.lang.StackOverflowError] with root cause
java.lang.StackOverflowError
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:95)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:549)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$ParameterizedTypeImpl.<init>($Gson$Types.java:453)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:99)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:542)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:549)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:542)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:549)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:542)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:549)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:542)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:549)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:542)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:549)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
at com.google.gson.internal.$Gson$Types$WildcardTypeImpl.<init>($Gson$Types.java:542)
at com.google.gson.internal.$Gson$Types.canonicalize($Gson$Types.java:108)
What am I missing? I've followed both request's step by step file upload using form as well as mykong's jersey multipart support.

You should look for somewhere in your configuration where the GSON package is used, in my experience that package is not stable to say the least and slow.
Try and change it to one of the better ones out there "jackson" for example, it might solve your problem.

Related

How to get InputStream from MultipartFormDataInput?

I'm trying to save pdf in wildfly, I'm using RestEasy MultipartFormDataInput provided with wildfly 20.0.1,
but it doesn't work.
This is what I have:
public static Response uploadPdfFile(MultipartFormDataInput multipartFormDataInput) {
// local variables
MultivaluedMap<String, String> multivaluedMap = null;
String fileName = null;
InputStream inputStream = null;
String uploadFilePath = null;
try {
Map<String, List<InputPart>> map = multipartFormDataInput.getFormDataMap();
List<InputPart> lstInputPart = map.get("poc");
for(InputPart inputPart : lstInputPart){
// get filename to be uploaded
multivaluedMap = inputPart.getHeaders();
fileName = getFileName(multivaluedMap);
if(null != fileName && !"".equalsIgnoreCase(fileName)){
try {
// write & upload file to UPLOAD_FILE_SERVER
//here I have the error: Unable to find a MessageBodyReader for media type:
//application/pdf
inputStream = inputPart.getBody(InputStream.class,InputStream.class);
uploadFilePath = writeToFileServer(inputStream, fileName);
}catch (Exception e) {
e.printStackTrace();
}
// close the stream
inputStream.close();
}
}
}
catch(IOException ioe){
ioe.printStackTrace();
}
finally{
// release resources, if any
}
return Response.ok("File uploaded successfully at " + uploadFilePath).build();
}
I'm using postman for test, http POST method, in the body I send: form-data - file and selected the file.pdf.
When I sent the request, I have the next RunTimeException when I try:
inputStream = inputPart.getBody(InputStream.class,null);
I get:
java.lang.RuntimeException: RESTEASY007545: Unable to find a MessageBodyReader for media type: application/pdf and class type org.jboss.resteasy.util.Base64$InputStream
At the moment I am saving the file receiving it in Base64, but I think that with MultipartFormDataInput it is the correct way.
This is what I have when debug:
Thanks for your support.
I solved this changing the InputStream from "org.jboss.resteasy.util.Base64.InputStream"
to "java.io.InputStream"

Do we have to pass header values from WebClient in Zipkins

I am using Spring boot and following libraries in client and server,
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2"
}
}
// Spring Cloud Sleuth
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-sleuth', version: '2.0.1.RELEASE'
compile group: 'org.springframework.cloud', name: 'spring-cloud-starter-zipkin', version: '2.0.1.RELEASE'
Based upon spring documentation, "https://cloud.spring.io/spring-cloud-sleuth/"
Run this app and then hit the home page. You will see traceId and spanId populated in the logs. If this app calls out to another one (e.g. with RestTemplate) it will send the trace data in headers and if the receiver is another Sleuth app you will see the trace continue there.
How will this work with Spring5 web client?
It will work in the same way. It's enough to inject a bean of WebClient or WebClientBuilder type. Check out this sample https://github.com/spring-cloud-samples/sleuth-documentation-apps/blob/master/service1/src/main/java/io/spring/cloud/sleuth/docs/service1/Service2Client.java
/**
* #author Marcin Grzejszczak
*/
#Component
class Service2Client {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final WebClient webClient;
private final String serviceAddress;
private final Tracer tracer;
Service2Client(WebClient webClient,
#Value("${service2.address:localhost:8082}") String serviceAddress,
Tracer tracer) {
this.webClient = webClient;
this.serviceAddress = serviceAddress;
this.tracer = tracer;
}
public String start() throws InterruptedException {
log.info("Hello from service1. Setting baggage foo=>bar");
Span span = tracer.currentSpan();
String secretBaggage = ExtraFieldPropagation.get("baggage");
log.info("Super secret baggage item for key [baggage] is [{}]", secretBaggage);
if (StringUtils.hasText(secretBaggage)) {
span.annotate("secret_baggage_received");
span.tag("baggage", secretBaggage);
}
String baggageKey = "key";
String baggageValue = "foo";
ExtraFieldPropagation.set(baggageKey, baggageValue);
span.annotate("baggage_set");
span.tag(baggageKey, baggageValue);
log.info("Hello from service1. Calling service2");
String response = webClient.get()
.uri("http://" + serviceAddress + "/foo")
.exchange()
.block()
.bodyToMono(String.class).block();
Thread.sleep(100);
log.info("Got response from service2 [{}]", response);
log.info("Service1: Baggage for [key] is [" + ExtraFieldPropagation.get("key") + "]");
return response;
}
#NewSpan("first_span")
String timeout(#SpanTag("someTag") String tag) {
try {
Thread.sleep(300);
log.info("Hello from service1. Calling service2 - should end up with read timeout");
String response = webClient.get()
.uri("http://" + serviceAddress + "/readtimeout")
.retrieve()
.onStatus(httpStatus -> httpStatus.isError(), clientResponse -> {
throw new IllegalStateException("Exception!");
})
.bodyToMono(String.class)
.block();
log.info("Got response from service2 [{}]", response);
return response;
} catch (Exception e) {
log.error("Exception occurred while trying to send a request to service 2", e);
throw new RuntimeException(e);
}
}
}

XmlHttpRequest and AEM Servlet

I am making an raw ajax call on the clientside, which look like the following,
var url = '/bin/denisa/dummyServlet';
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
xhr.setRequestHeader('Accept', 'application/json, text/javascript, */*; q=0.01');
xhr.onload = function() {
if (xhr.status === 200) {
alert('deni');
}
else {
alert('Request failed. Returned status of ' + xhr.status);
}
};
xhr.send();
to the following servlet in AEM:
#Component(
service = {
Servlet.class
},
property = {
"sling.servlet.paths=/bin/denisa/dummyServlet",
"sling.servlet.extensions=json",
"sling.servlet.methods=GET"
}
)
public class DummyServlet extends SlingSafeMethodsServlet {
private static final Logger LOG = LoggerFactory.getLogger(DummyServlet .class);
#Reference
private dummyService dummyService;
#Override
protected void doGet(SlingHttpServletRequest request, SlingHttpServletResponse response) throws ServletException, IOException {
Gson gson = new Gson();
JsonElement jsonElement = gson.toJsonTree(dummyService);
response.setContentType("application/json");
response.getWriter().write(gson.toJson(jsonElement));
}
}
And I got a 404, and i don't know exactly why because the path is the same, and also contentTypes. Does anybody has a clue?
Your code looks good it seems something is missing on the config side
can you log in to the felix console
http://localhost:4502/system/console/bundles
Expand you project bundle and check for your servlet
with Service ID xx Types: javax.servlet.Servlet and other details
*Also ensure the bundle is not installed and in active state
Then go to http://localhost:4502/system/console/configMgr and verify Apache Sling Servlet/Script Resolver execution paths contain /bin/ entry
If above mentioned configs are navigate to http://localhost:4502/system/console/servletresolver
check for servlet response.
These are some of the configuration which helped me to resolve 404

File upload for Netty 4.x example

How make upload http file server with Netty 4.x ? For 3.x is an example, for 4.x example only for serving static file.
I use netty-all:4.1.32.Final do upload file server
// Post request
private void formParams(HttpServerRequest request, ByteBuf content, Map<String, String> formParams, Map<String, MemoryFileUpload> fileParams) {
if (content != null) {
// POST Params
FullHttpRequest dhr = new DefaultFullHttpRequest(request.version(), request.method(), request.uri(), content, request.requestHeaders(), EmptyHttpHeaders.INSTANCE);
HttpPostRequestDecoder postDecoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), dhr);
List<InterfaceHttpData> postData = postDecoder.getBodyHttpDatas();
for (InterfaceHttpData data : postData) {
// General Post Content
if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
MemoryAttribute attribute = (MemoryAttribute) data;
formParams.put(attribute.getName(), attribute.getValue());
}
// Upload
else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
MemoryFileUpload fileUpload = (MemoryFileUpload) data;
fileParams.put(fileUpload.getName(), fileUpload);
}
}
}
}
HttpPostRequestDecoder is not ported to 4.0.0.x yet. So there is no support for it atm. It's on the to-do-list.

JavaFX: File upload to REST service / servlet fails because of missing boundary

I'm trying to upload a file using JavaFX using the HttpRequest. For this purpose I have written the following function.
function uploadFile(inputFile : File) : Void {
// check file
if (inputFile == null or not(inputFile.exists()) or inputFile.isDirectory()) {
return;
}
def httpRequest : HttpRequest = HttpRequest {
location: urlConverter.encodeURL("{serverUrl}");
source: new FileInputStream(inputFile)
method: HttpRequest.POST
headers: [
HttpHeader {
name: HttpHeader.CONTENT_TYPE
value: "multipart/form-data"
}
]
}
httpRequest.start();
}
On the server side, I am trying to handle the incoming data using the Apache Commons FileUpload API using a Jersey REST service. The code used to do this is a simple copy of the FileUpload tutorial on the Apache homepage.
#Path("Upload")
public class UploadService {
public static final String RC_OK = "OK";
public static final String RC_ERROR = "ERROR";
#POST
#Produces("text/plain")
public String handleFileUpload(#Context HttpServletRequest request) {
if (!ServletFileUpload.isMultipartContent(request)) {
return RC_ERROR;
}
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = null;
try {
items = upload.parseRequest(request);
}
catch (FileUploadException e) {
e.printStackTrace();
return RC_ERROR;
}
...
}
}
However, I get a exception at items = upload.parseRequest(request);:
org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
I guess I have to add a manual boundary info to the InputStream. Is there any easy solution to do this? Or are there even other solutions?
Have you tried just using the InputStream from HttpServletRequest like so
InputStream is = httpRequest.getInputStream();
BufferedInputStream in = new BufferedInputStream(is);
//Write out bytes
out.close();
is.close();