My code for RESTful file upload :
#Path("/upload")
#POST
#Consumes("multipart/form-data")
public String post(
#FormDataParam("part") String s,
#FormDataParam("part") FormDataContentDisposition d) {
return s + ":" + d.getFileName();
}
When I try to upload a file using curl
curl -X POST --form part=#file.txt url
I am getting a HTTP 415-Unsupported Media Type Error. What is wrong ?
After trying a lot of examples finaly find the realy working example on http://iambigd.blogspot.com/2011/06/java-upload-file-using-jersey.html
#POST
#Path("/simpleupload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public void simpleUpload(
//#Context UriInfo ui,
#Context HttpServletRequest request
){
String fileRepository = "D:\\";
if (ServletFileUpload.isMultipartContent(request)) {
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = null;
try {
items = upload.parseRequest(request);
} catch (FileUploadException e) {
e.printStackTrace();
}
if (items != null) {
Iterator<FileItem> iter = items.iterator();
while (iter.hasNext()) {
FileItem item = iter.next();
if (!item.isFormField() && item.getSize() > 0) {
System.out.println("File is found.");
String fileName = processFileName(item.getName());
try {
String savePath = fileRepository + fileName;
System.out.println("savePath:" + savePath);
item.write(new File(savePath));
} catch (Exception e) {
e.printStackTrace();
}
}else{
System.out.println("getFieldName:" + item.getFieldName());
System.out.println(item.getString());
}
}
}
}
}
(need the servlet-api.jar, (apache) commons-oi.jar and (apache) commons-fileupload.jar)
This can happen due to a couple of reasons. I managed to narrow down some of them.
Your Content-Type header does not match with the one provided by the #Consumes header. Verify this with a proxy.
You managed to stumble upon a bug that was fixed in Jersey 1.4 related to the FormDataParam annotation.
You included jersey-bundle and jersey-server et all in the same binary and they are competing against each other.
You are using #FormParam instead of #FormDataParam.
Your #FormDataParam is unrecognized by the introspection API because of conflicts with jersey-multipart and other jersey jars. If one jar is of version 1.x make sure the other jars are on the same version. While debugging the jersey API code I noticed that these method annotations turn up blank (on jersey's code) if the jar versions are not uniform. All method parameters on the REST service are replaced by the body content of the POST request irrespective of which FormDataParam they are supposed to contain.
Please make sure you have mimepull.jar on the classpath
You may need to register the MultipartFeature as described in the Jersey documentation, chapter 8.3.1.2 Registration.
Create a class something like this:
/**
*
*/
package com.verico.multipart.app;
import javax.ws.rs.ApplicationPath;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.server.ResourceConfig;
#ApplicationPath("/")
public class MultiPartApp extends ResourceConfig {
public MultiPartApp() {
super(MultiPartFeature.class);
}
}
And add the following init-param to your Jersey servlet in web.xml:
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>com.verico.multipart.app.MultiPartApp</param-value>
</init-param>
Have you tried with an Input stream ?
Like :
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response post(
#Context HttpServletRequest request,
#Context HttpHeaders headers,
#FormDataParam("file") InputStream fileStream,
Works fine for me.
Related
So i use Amdatu inside a Felix framework to create an OSGi enabled JSON Rest Service.
When i use #GET i get the id value as expected
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("file")
public String getFile(#QueryParam("id") String id) {
System.out.println("id : "+id);
return null;
}
When i use #POST FormParam is always null
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Path("file")
public String getFile(#FormParam("id") String id) {
System.out.println("id : "+id);
return null;
}
When i use #POST but with application JSON i always get the entire raw json and not the value.
I followed this video : http://www.amdatu.org/howto/createwebapp.html
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Path("file")
public String getFile(String id) throws Exception {
return id
}
I'm using the advanced rest client plugin for chrome to test the service.
Using libraries
org.amdatu.web.rest.jaxrs:1.0.4
org.amdatu.web.rest.wink:1.0.8
jackson-jaxrs:1.9.13
jackson-mapper-asl:1.9.13
jackson-core-asl:1.9.13
Update :
I had the depedencies in my maven bundle set to "provided" by changing them to "compile" MediaType.APPLICATION_FORM_URLENCODED now works.
But the MediaType.MULTIPART_FORM_DATA still doesnt.
During the post of the form my header is :
Content-Type: multipart/form-data
if i remove the #FormParam then the id is filled with :
id : --ARCFormBoundary5xbnwa6as8aor
Content-Disposition: form-data; name="id"
9
--ARCFormBoundary5xbnwa6as8aor--
the moment i add #FormParam the value is null.
The JAX-RS specification doesn't say anything about how multipart/form-data should be handled. Different JAX-RS implementations have their own proprietary ways to deal with this. As far as I can find, Apache Wink (that we build on top of) doesn't support #FormParam for multipart. It does seems that there are some support types in Wink for multi part: http://wink.apache.org/documentation/1.1.1/html/7.8%20MultiPart.html but these classes are not exposed by the Amdatu Wink bundle, and I have never tried using this either.
A workaround I use myself is the example below. This is useful when multipart is used to upload files together with other form fields (this is why multipart is mostly used). I use Apache File Upload to parse the request and get access to both the uploaded files and form fields.
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
public void test(#Context HttpServletRequest request) {
ServletFileUpload uploader = new ServletFileUpload(new DiskFileItemFactory());
try {
List<FileItem> parseRequest = uploader.parseRequest(request);
for (FileItem fileItem : parseRequest) {
if (fileItem.isFormField()) {
System.out.println(fileItem.getFieldName() + ": "
+ fileItem.getString());
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
It looks like you're using the wrong Mediatype in #Consumes.
I created an example (see below) that accepts a parameter from a form, either by omitting the #Consumes completely, or setting it to MediaType.APPLICATION_FORM_URLENCODED.
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void test(#FormParam("id") String id) {
System.out.println(id);
}
HTML
<form action="/agenda" method="post">
<input type="id" name="id"/>
<input type="submit" value="Test">
</form>
I tried to receive files via restlet but only gets the complete MULTIPART_FORM_DATA.
How can I extract my specific file?
I found some code-blocks but the types of them are not available...
RESTlet: How to process multipart/form-data requests?
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setSizeThreshold(1000240);
// 2/ Create a new file upload handler
RestletFileUpload upload = new RestletFileUpload(factory);
My current code:
#Put
public void handleUpload(Representation entity) {
if (entity !=null) {
if (MediaType.MULTIPART_FORM_DATA.equals(entity.getMediaType(), true)) {
Request restletRequest = getRequest();
Response restletResponse = getResponse();
HttpServletRequest servletRequest = ServletUtils.getRequest(restletRequest);
HttpServletResponse servletResponse = ServletUtils.getResponse(restletResponse);
try {
Upload2(restletRequest, entity);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
public void Upload2(Request req, Representation entity) throws IOException
{
...
GcsOutputChannel outputChannel = gcsService.createOrReplace(fileName, GcsFileOptions.getDefaultInstance());
ObjectOutputStream oout = new ObjectOutputStream(Channels.newOutputStream(outputChannel));
copy(entity.getStream(), oout);
oout.close();
After storing with this method I have something like that and I only want to store the content with the name "picture":
��z------WebKitFormBoundarysOvzWKPqyqW7DiTu
Content-Disposition: form-data; name="picture"; filename="_MG_4369.jpg"
Content-Type: image/jpeg
����*ExifII*
As far as I read parsing of multipart form data isn’t supported yet? But there must be a solution to send files via restlet
I tried the rest-calls via postman for chrome. The is only on multiparts the support for files. Is a possible solution to send the image as a raw-text?
You will need to add Exception handling and deal with the InputStream and potentially clean up of temp files (see DiskFileItemFactory docs) but the basics are as follows, when using the org.restlet.ext.fileupload library.
#Put
public void handleUpload(Representation entity) {
List<FileItem> items = new RestletFileUpload(new DiskFileItemFactory())
.parseRepresentation(representation);
for (FileItem item : items) {
if (!item.isFormField()) {
MediaType type = MediaType.valueOf(item.getContentType());
InputStream inputStream = item.getInputStream();
}
}
}
for a gae Solution try replacing the DiskFileItemFactory with a gwtupload.server.MemoryFileItemFactory.
You should send your file uploads as an InputStream. Then you can use this:
#Put
public void handleUpload(InputStream entity) {
}
The file is now its stream and will no longer have form data inside of it. To send a file as a stream, you can set up a client in java (using jersey, for example).
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
File f = new File("C:/file/to/upload.zip");
InputStream data = new FileInputStream(f);
Client client = Client.create();
client.setChunkedEncodingSize(1024);
WebResource resource = client.resource("http://localhost:80/your/uri");
ClientResponse response = resource.accept("application/x-octet-stream").type("application/x-octet-stream").post(ClientResponse.class, data);
Now that you have data, you can set up a client to post data to your server. I was trying this using multipart form data and postman before. I went mad trying to get multipart data to work. But this is the solution I have been using instead. And it works perfectly.
I want to upload a file (a zip file to be specific) to a Jersey-backed REST server.
Basically there are two approaches (I mean using Jersey Client, otherwise one can use pure servlet API or various HTTP clients) to do this:
1)
WebResource webResource = resource();
final File fileToUpload = new File("D:/temp.zip");
final FormDataMultiPart multiPart = new FormDataMultiPart();
if (fileToUpload != null) {
multiPart.bodyPart(new FileDataBodyPart("file", fileToUpload, MediaType.valueOf("application/zip")));
}
final ClientResponse clientResp = webResource.type(MediaType.MULTIPART_FORM_DATA_TYPE).post(
ClientResponse.class, multiPart);
System.out.println("Response: " + clientResp.getClientResponseStatus());
2)
File fileName = new File("D:/temp.zip");
InputStream fileInStream = new FileInputStream(fileName);
String sContentDisposition = "attachment; filename=\"" + fileName.getName() + "\"";
ClientResponse response = resource().type(MediaType.APPLICATION_OCTET_STREAM)
.header("Content-Disposition", sContentDisposition).post(ClientResponse.class, fileInStream);
System.out.println("Response: " + response.getClientResponseStatus());
For sake of completeness here is the server part:
#POST
#Path("/import")
#Consumes({MediaType.MULTIPART_FORM_DATA, MediaType.APPLICATION_OCTET_STREAM})
public void uploadFile(File theFile) throws PlatformManagerException, IOException {
...
}
So I am wondering what is the difference between those two clients?
Which one to use and why?
Downside (for me) of using 1) approach is that it adds dependency on jersey-multipart.jar (which additionally adds dependency on mimepull.jar) so why would I want those two jars in my classpath if pure Jersey Client approach 2) works just as fine.
And maybe one general question is whether there is a better way to implement ZIP file upload, both client and server side...
Approach 1 allows you to use multipart features, for example, uploading multiple files at the same time, or adding extra form to the POST.
In which case you can change the server side signature to:
#POST
#Path("upload")
#Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadMultipart(FormDataMultiPart multiPart) throws IOException {
}
I also found that I had to register the MultiPartFeature in my test client...
public FileUploadUnitTest extends JerseyTest {
#Before
public void before() {
// to support file upload as a test client
client().register(MultiPartFeature.class);
}
}
And server
public class Application extends ResourceConfig {
public Application() {
register(MultiPartFeature.class);
}
}
Thanks for your question, it helped me write my jersey file unit test!
I got a HttpServletRequest request in my Spring Servlet which I would like to forward AS-IS (i.e. GET or POST content) to a different server.
What would be the best way to do it using Spring Framework?
Do I need to grab all the information and build a new HTTPUrlConnection? Or there is an easier way?
Discussions of whether you should do forwarding this way aside, here's how I did it:
package com.example.servlets;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Enumeration;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.example.servlets.GlobalConstants;
#SuppressWarnings("serial")
public class ForwardServlet extends HttpServlet {
#Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) {
forwardRequest("GET", req, resp);
}
#Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) {
forwardRequest("POST", req, resp);
}
private void forwardRequest(String method, HttpServletRequest req, HttpServletResponse resp) {
final boolean hasoutbody = (method.equals("POST"));
try {
final URL url = new URL(GlobalConstants.CLIENT_BACKEND_HTTPS // no trailing slash
+ req.getRequestURI()
+ (req.getQueryString() != null ? "?" + req.getQueryString() : ""));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(method);
final Enumeration<String> headers = req.getHeaderNames();
while (headers.hasMoreElements()) {
final String header = headers.nextElement();
final Enumeration<String> values = req.getHeaders(header);
while (values.hasMoreElements()) {
final String value = values.nextElement();
conn.addRequestProperty(header, value);
}
}
//conn.setFollowRedirects(false); // throws AccessDenied exception
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(hasoutbody);
conn.connect();
final byte[] buffer = new byte[16384];
while (hasoutbody) {
final int read = req.getInputStream().read(buffer);
if (read <= 0) break;
conn.getOutputStream().write(buffer, 0, read);
}
resp.setStatus(conn.getResponseCode());
for (int i = 0; ; ++i) {
final String header = conn.getHeaderFieldKey(i);
if (header == null) break;
final String value = conn.getHeaderField(i);
resp.setHeader(header, value);
}
while (true) {
final int read = conn.getInputStream().read(buffer);
if (read <= 0) break;
resp.getOutputStream().write(buffer, 0, read);
}
} catch (Exception e) {
e.printStackTrace();
// pass
}
}
}
Obviously this could use a bit of work with regard to error handling and the like but it was functional. I stopped using it, however, because it was easier in my case to make calls directly to the CLIENT_BACKEND than to deal with cookies, auth, etc. across two distinct domains.
I also needed to do the same, and after some non optimal with Spring controllers and RestTemplate, I found a better solution: Smiley's HTTP Proxy Servlet. The benefit is, it really does AS-IS proxying, just like Apache's mod_proxy, and it does it in a streaming way, without caching the full request/response in the memory.
Simply, you register a new servlet to the path you want to proxy to another server, and give this servlet the target host as an init parameter. If you are using a traditional web application with a web.xml, you can configure it like following:
<servlet>
<servlet-name>proxy</servlet-name>
<servlet-class>org.mitre.dsmiley.httpproxy.ProxyServlet</servlet-class>
<init-param>
<param-name>targetUri</param-name>
<param-value>http://target.uri/target.path</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>proxy</servlet-name>
<url-pattern>/mapping-path/*</url-pattern>
</servlet-mapping>
or, of course, you can go with the annotation config.
If you are using Spring Boot, it is even easier: You only need to create a bean of type ServletRegistrationBean, with the required configuration:
#Bean
public ServletRegistrationBean proxyServletRegistrationBean() {
ServletRegistrationBean bean = new ServletRegistrationBean(
new ProxyServlet(), "/mapping-path/*");
bean.addInitParameter("targetUri", "http://target.uri/target.path");
return bean;
}
This way, you can also use the Spring properties that are available in the environment.
You can even extend the class ProxyServlet and override its methods to customize request/response headers etc, in case you need.
Update: After using Smiley's proxy servlet for some time, we had some timeout issues, it was not working reliably. Switched to Zuul from Netflix, didn't have any problems after that. A tutorial on configuring it with Spring Boot can be found on this link.
Unfortunately there is no easy way to do this. Basically you'll have to reconstruct the request, including:
correct HTTP method
request parameters
requests headers (HTTPUrlConnection doesn't allow to set arbitrary user agent, "Java/1.*" is always appended, you'll need HttpClient)
body
That's a lot of work, not to mention it won't scale since each such proxy call will occupy one thread on your machine.
My advice: use raw sockets or netty and intercept HTTP protocol on the lowest level, just replacing some values (like Host header) on the fly. Can you provide more context, why so you need this?
#RequestMapping(value = "/**")
public ResponseEntity route(HttpServletRequest request) throws IOException {
String body = IOUtils.toString(request.getInputStream(), Charset.forName(request.getCharacterEncoding()));
try {
ResponseEntity<Object> exchange = restTemplate.exchange(firstUrl + request.getRequestURI(),
HttpMethod.valueOf(request.getMethod()),
new HttpEntity<>(body),
Object.class,
request.getParameterMap());
return exchange;
} catch (final HttpClientErrorException e) {
return new ResponseEntity<>(e.getResponseBodyAsByteArray(), e.getResponseHeaders(), e.getStatusCode());
}
}
If you are forced to use spring, please check the rest template method exchange to proxy requests to a third party service.
Here you can find a working example.
Use Spring Cloud Gateway
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-mvc</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Controller
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.mvc.ProxyExchange;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
#RestController
public class Proxy extends BaseController {
private String prefix="/proxy";
private String Base="localhost:8080/proxy"; //destination
void setHeaders(ProxyExchange<?> proxy){
proxy.header("header1", "val1"); //add additional headers
}
#GetMapping("/proxy/**")
public ResponseEntity<?> proxyPath(#RequestParam MultiValueMap<String,String> allParams, ProxyExchange<?> proxy) throws Exception {
String path = proxy.path(prefix); //extract sub path
proxy.header("Cache-Control", "no-cache");
setHeaders(proxy);
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(Base + path).queryParams(allParams).build();
return proxy.uri(uriComponents.toUri().toString()).get();
}
#PutMapping("/proxy/**")
public ResponseEntity<?> proxyPathPut(ProxyExchange<?> proxy) throws Exception {
String path = proxy.path(prefix);
setHeaders(proxy);
return proxy.uri(Base + path).put();
}
I am not using JSON or anything like that. I have a simple form to upload a file and I want to read the parameters of the form. The code below is not working as expected. It will not show any parameters.
#POST
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Path("{appNum}/{docId}/file")
public Response uploadDocFile(
#PathParam("appNum") String appNum,
#PathParam("docId") String docId,
#Context HttpServletRequest req)
{
try {
log.info("POST Parameters:");
Enumeration e = req.getParameterNames();
while(e.hasMoreElements())
{
Object key = e.nextElement();
log.info("Key: " + key);
log.info("Val: " + req.getParameter(key.toString()));
}
} catch (Exception e) {
e.printStackTrace();
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(new StatusResponse(e)).build();
}
return Response.ok().build();
}
FYI, You need to use #FormParam. Also make sure INPUT HTML types are using name= not id=.
I have the same problem. Using #FormParam annotation for individual parameters works, but reading them from HttpServletRequest injected through #Context doesn't. I also tried to get the request object/parameters through Guice using Provider<HttpServletRequest> and #RequestParameters<Map<String, String[]>>. In both cases there were no post parameters.
However, it is possible to get a map of parameters by adding a MultivaluedMap<String, String> parameter to resource method. Example:
#POST
public void doSomething(MultivaluedMap<String, String> formParams) {
//...
}
If you are using Jersey RESTful API in JAVA you can look for Parameter Annotations (#*Param)
Example:
Dependency:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.8</version>
</dependency>
Code:
package yourpack;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Response;
#Path("/path_to_data")
public class DataResource {
#GET
#Path("/{param}")
public Response getMsg(#PathParam("param") String urlparam) {
int ok = 200;
String result = "Jersey Data resource: " + urlparam;
return Response.status(ok).entity(result ).build();
}
}
List of annotations: #MatrixParam, #HeaderParam, #CookieParam, #FormParam, #QueryParam, #PathParam
At some point of time Jersey ContainerServlet (or other Jersey object during request processing) calls request.getInputStream() or request.getReader() which set 'usingInputStream' or 'usingReader' to TRUE. This state prevents populating of parameters map inside the request object. Something like this:
parseParameters() {
if (usingInputStream || usingReader) {
return;
} else {
parametersMap.putAll({actual parameters parsing from stream})
}
}
Map getParametersMap() {
return parametersMap;
}
Try putting a break point at the very first entry point (beginning of Jersey ServletContainer.service() method) of your application and evaluate request.getParametersMap() call. You'll get your parameters.