Apache Jersey Client Multipart Upload Mp4 as Octlet-stream?
Every RESTful API grows to the point information technology needs to support file uploads. Supporting file uploads with Jersey is rather simple and in this mail I will show you how to practise information technology. All my posts come with a GitHub project, and this mail service is no exception.
The GitHub repository for this post tin can be found at:
- https://github.com/bytefish/JerseyFileUploadExample
Dependencies
Yous need the bailiwick of jersey-media-multipart
package to support HTTP multipart requests with Jersey. I am using Jump Boot for the example, so you volition also demand the spring-boot-starter-jersey
depdendency.
In the dependencies
element of the POM file we add together both dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>bound-kicking-starter-jersey</artifactId> </dependency> <dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>bailiwick of jersey-media-multipart</artifactId> <version>ii.25</version> </dependency>
Project Construction
Information technology'southward useful to take a expect at the project construction offset:
The purpose of the various classes:
-
exceptions
-
FileUploadException
- An exception, that volition be thrown if a File Upload fails due to an fault.
-
-
handler
-
IFileUploadHandler
- This interface needs to be implemented to handle incoming file upload requests.
-
LocalStorageHandler
- A default implementation of the
IFileUploadHandler
, which writes a file to the local filesystem.
- A default implementation of the
-
-
model
-
errors
-
ServiceError
- An error containing an error code and error reason for failed uploads.
-
HttpServiceError
- Wraps a
ServiceError
and adds a HTTP Condition Code, which will be sent dorsum to the client.
- Wraps a
-
-
files
-
HttpFile
- Abstracts the incoming HTTP multipart file data into something more than usable for business logic.
-
-
asking
-
FileUploadRequest
- Abstracts the incoming HTTP multipart class information and builds a Request with Metadata and a
HttpFile
.
- Abstracts the incoming HTTP multipart class information and builds a Request with Metadata and a
-
-
response
-
FileUploadResponse
- The response information, that will be sent to the client.
-
-
-
provider
-
IRootPathProvider
- This interface needs to be implemented for defining the Servers root path.
-
RootPathProvider
- The default implementation of the
IRootPathProvider
.
- The default implementation of the
-
-
web
-
configuration
-
JerseyConfiguration
- The Bailiwick of jersey configuration setting up the Jersey server.
-
-
exceptions
-
FileUploadExceptionMapper
- Handles Exceptions on the highest level and turns them into a useful representation.
-
-
resource
-
FileUploadResource
- Finally the resource implementing the File Upload API.
-
-
-
SampleJerseyApplication
- Configures and starts the Server.
Domain Model
Errors
In nearly of my posts I stress how of import errors are. It's because you need to provide a proper approach to errors as early equally possible in your projects. It should be possible for a client to make sense of errors, so you don't have to investigate log files for every problem.
And more importantly, you lot don't want to leak Exception details to the client. Your Stacktrace may contain sensitive information and may reveal details virtually your architecture, that should be hidden. And I approximate you lot don't desire your exception letters to exist sent into the wild.
ServiceError
An error sent to the client should incorporate a code
and an error message
. Providing a specific fault code makes it possible for a client to automatically evaluate the error response and have the right actions, such as notifying users to provide required data.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Come across LICENSE file in the project root for full license information. bundle de.bytefish.fileuploads.model.errors ; import com.fasterxml.jackson.annotation.JsonProperty ; public class ServiceError { private final String code ; individual concluding String message ; public ServiceError ( String code , String message ) { this . lawmaking = lawmaking ; this . message = bulletin ; } @JsonProperty ( "code" ) public Cord getCode () { return code ; } @JsonProperty ( "message" ) public String getMessage () { return bulletin ; } }
HttpServiceError
The API volition exist a RESTful API, which defined HTTP Status Codes to indicate failure and success. There may be different types of errors, like a bad request (HTTP Status 400), invalid authentication credentials (HTTP Condition 401). It should also exist possible to send an Internal Server Error (HTTP Status 500) to the client, which says the error is on the Server side and cannot be fixed by the client.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Encounter LICENSE file in the project root for total license information. package de.bytefish.fileuploads.model.errors ; public class HttpServiceError { private final int httpStatusCode ; private concluding ServiceError serviceError ; public HttpServiceError ( int httpStatusCode , ServiceError serviceError ) { this . httpStatusCode = httpStatusCode ; this . serviceError = serviceError ; } public int getHttpStatusCode () { return httpStatusCode ; } public ServiceError getServiceError () { return serviceError ; } }
FileUploadException
If a file can't be uploaded due to missing or invalid data, and so the control menstruum will be exited by throwing an awarding. The FileUploadException
gets a ServiceError
passed into and wraps information technology in a HttpServiceError
. If someone in the application thinks it's appropriate to handle the exception, then the method getHttpServiceError()
returns the error reason.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Run into LICENSE file in the projection root for full license information. package de.bytefish.fileuploads.exceptions ; import de.bytefish.fileuploads.model.errors.HttpServiceError ; import de.bytefish.fileuploads.model.errors.ServiceError ; public class FileUploadException extends RuntimeException { private final HttpServiceError httpServiceError ; public FileUploadException ( ServiceError serviceError ) { this . httpServiceError = createServiceError ( serviceError ); } public FileUploadException ( ServiceError serviceError , Cord message ) { super ( message ); this . httpServiceError = createServiceError ( serviceError ); } public FileUploadException ( ServiceError serviceError , String message , Throwable cause ) { super ( bulletin , crusade ); this . httpServiceError = createServiceError ( serviceError ); } public HttpServiceError getHttpServiceError () { return httpServiceError ; } private static HttpServiceError createServiceError ( ServiceError serviceError ) { return new HttpServiceError ( 400 , serviceError ); } }
FileUploadExceptionMapper
The best place to handle such an exception is in the web layer. Jersey provides a so called ExceptionMapper
, which makes it possible to handle specific exceptions. In the instance the FileUploadException
is handled and a response is generated, with the HTTP Status code and ServiceError
extracted from the exception.
bundle de.bytefish.fileuploads.web.exceptions ; import de.bytefish.fileuploads.exceptions.FileUploadException ; import de.bytefish.fileuploads.model.errors.HttpServiceError ; import org.slf4j.Logger ; import org.slf4j.LoggerFactory ; import javax.ws.rs.core.Response ; public grade FileUploadExceptionMapper implements javax . ws . rs . ext . ExceptionMapper < FileUploadException > { private static final Logger logger = LoggerFactory . getLogger ( FileUploadExceptionMapper . grade ); @Override public Response toResponse ( FileUploadException fileUploadException ) { if ( logger . isErrorEnabled ()) { logger . mistake ( "An error occured" , fileUploadException ); } HttpServiceError httpServiceError = fileUploadException . getHttpServiceError (); return Response . status ( httpServiceError . getHttpStatusCode ()) . entity ( httpServiceError . getServiceError ()) . build (); } }
Abstracting the Multipart File Data
HttpFile
Even minor applications tin can grow into larger systems. So y'all want to decouple your system as early as possible in a project. 1 of these abstractions should be turning the incoming HTTP multipart request into something more useful. Considering you really don't want to fiddle around with a HttpServletRequest
deep down in your business logic; nor should whatever other developer in your team do so.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. See LICENSE file in the projection root for full license information. bundle de.bytefish.fileuploads.model.files ; import java.io.InputStream ; import java.util.Map ; public grade HttpFile { private final String proper noun ; private final String submittedFileName ; private final long size ; private final Map < String , String > parameters ; private final InputStream stream ; public HttpFile ( String name , Cord submittedFileName , long size , Map < Cord , String > parameters , InputStream stream ) { this . proper name = name ; this . submittedFileName = submittedFileName ; this . size = size ; this . parameters = parameters ; this . stream = stream ; } public String getName () { return name ; } public String getSubmittedFileName () { render submittedFileName ; } public long getSize () { return size ; } public Map < String , String > getParameters () { return parameters ; } public InputStream getStream () { render stream ; } }
FileUploadRequest
Every file upload may contain some metadata similar a title or the description. This metadata shouldn't pollute the HttpFile
, and so nosotros wrap the HttpFile
and add together the Metadata in a FileUploadRequest
.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed nether the MIT license. Encounter LICENSE file in the projection root for full license information. package de.bytefish.fileuploads.model.request ; import de.bytefish.fileuploads.model.files.HttpFile ; public class FileUploadRequest { private last Cord title ; individual final String description ; individual final HttpFile httpFile ; public FileUploadRequest ( Cord title , String description , HttpFile httpFile ) { this . title = championship ; this . clarification = description ; this . httpFile = httpFile ; } public String getTitle () { return title ; } public String getDescription () { return clarification ; } public HttpFile getHttpFile () { return httpFile ; } }
FileUploadResponse
If you are storing a file on the server, so chances are good there are indistinguishable file names when using the original file name. These files shouldn't exist overriden, so one fashion is to assign a unique identifier to the file, and laissez passer it to the client. The client itself then knows, which file he has to request.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed nether the MIT license. See LICENSE file in the project root for full license information. package de.bytefish.fileuploads.model.response ; import com.fasterxml.jackson.annotation.JsonProperty ; public class FileUploadResponse { private final Cord identifier ; public FileUploadResponse ( String identifier ) { this . identifier = identifier ; } @JsonProperty ( "identifier" ) public String getIdentifier () { return identifier ; } }
Treatment the incoming data
Determining the Root Path of the Server
The user should be able to decide, where to write files and this is where the IRootPathProvider
is necessary. An IRootPathProvider
is a simple interface, that but returns the root path to write the files to, if the implementation is using a local filesystem.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Encounter LICENSE file in the project root for total license information. package de.bytefish.fileuploads.provider ; public interface IRootPathProvider { Cord getRootPath (); }
The default implementation RootPathProvider
requires the root path to be configured explicitly, when bootstrapping the server.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for total license information. package de.bytefish.fileuploads.provider ; public class RootPathProvider implements IRootPathProvider { private concluding String path ; public RootPathProvider ( String path ) { this . path = path ; } @Override public String getRootPath () { return path ; } }
FileUploadHandler
With a proper model in place, the abstraction for handling a FileUploadRequest
becomes elementary:
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Come across LICENSE file in the projection root for full license data. package de.bytefish.fileuploads.handler ; import de.bytefish.fileuploads.model.request.FileUploadRequest ; import de.bytefish.fileuploads.model.response.FileUploadResponse ; public interface IFileUploadHandler { FileUploadResponse handle ( FileUploadRequest request ); }
The default implementation writes to the local filesystem, that's why I called it a LocalStorageFileUploadHandler
. You can run across, that the handler gets an IRootPathProvider
injected, so you can hands configure the root path from the outside.
The LocalStorageFileUploadHandler
kickoff evaluates, if a file is bachelor in the request. If some assertions don't agree, the control flow volition be stopped and a FileUploadException
is thrown, with a ServiceError
included.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Meet LICENSE file in the project root for full license data. package de.bytefish.fileuploads.handler ; import de.bytefish.fileuploads.exceptions.FileUploadException ; import de.bytefish.fileuploads.model.files.HttpFile ; import de.bytefish.fileuploads.model.errors.ServiceError ; import de.bytefish.fileuploads.model.request.FileUploadRequest ; import de.bytefish.fileuploads.model.response.FileUploadResponse ; import de.bytefish.fileuploads.provider.IRootPathProvider ; import org.springframework.beans.manufactory.annotation.Autowired ; import org.springframework.stereotype.Component ; import java.io.InputStream ; import coffee.nio.file.Files ; import coffee.nio.file.Paths ; import coffee.util.UUID ; @Component public course LocalStorageFileUploadHandler implements IFileUploadHandler { private final IRootPathProvider rootPathProvider ; @Autowired public LocalStorageFileUploadHandler ( IRootPathProvider rootPathProvider ) { this . rootPathProvider = rootPathProvider ; } @Override public FileUploadResponse handle ( FileUploadRequest request ) { // Early exit, if in that location is no Asking: if ( request == zilch ) { throw new FileUploadException ( new ServiceError ( "missingFile" , "Missing File data" ), String . format ( "Missing Parameter: request" )); } // Become the HttpFile: HttpFile httpFile = asking . getHttpFile (); // Early exit, if the Request has no data assigned: if ( httpFile == nix ) { throw new FileUploadException ( new ServiceError ( "missingFile" , "Missing File data" ), String . format ( "Missing Parameter: request.httpFile" )); } // We don't override existing files, create a new UUID File name: String targetFileName = UUID . randomUUID (). toString (); // Write information technology to Disk: internalWriteFile ( httpFile . getStream (), targetFileName ); return new FileUploadResponse ( targetFileName ); } private void internalWriteFile ( InputStream stream , String fileName ) { attempt { Files . copy ( stream , Paths . get ( rootPathProvider . getRootPath (), fileName )); } catch ( Exception e ) { throw new FileUploadException ( new ServiceError ( "storingFileError" , "Error writing file" ), String . format ( "Writing File '%s' failed" , fileName ), eastward ); } } }
The Web Layer
Resource
The FileUploadResource
at present connects all the parts. It defines an endpoint, that consumes multipart form data and produces a JSON Response. The fileUpload(...)
method uses the @FormDataParam
annotation from the jersey-media-multipart
bundle to evaluate the incoming HTTP multipart class data request.
From the incoming request we first build the HttpFile
, the FileUploadRequest
and then pass it into the configured IFileUploadHandler
. If the file was written successfully, then the FileUploadResponse
with a unique file identifier is returned to the client.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Come across LICENSE file in the project root for full license data. package de.bytefish.fileuploads.spider web.resource ; import de.bytefish.fileuploads.handler.IFileUploadHandler ; import de.bytefish.fileuploads.model.files.HttpFile ; import de.bytefish.fileuploads.model.request.FileUploadRequest ; import de.bytefish.fileuploads.model.response.FileUploadResponse ; import org.glassfish.jersey.media.multipart.FormDataContentDisposition ; import org.glassfish.bailiwick of jersey.media.multipart.FormDataParam ; import org.springframework.beans.factory.notation.Autowired ; import org.springframework.stereotype.Component ; import javax.ws.rs.Consumes ; import javax.ws.rs.POST ; import javax.ws.rs.Path ; import javax.ws.rs.Produces ; import javax.ws.rs.core.MediaType ; import javax.ws.rs.core.Response ; import coffee.io.InputStream ; @Component @Path ( "/files" ) public form FileUploadResource { private concluding IFileUploadHandler fileUploadHandler ; @Autowired public FileUploadResource ( IFileUploadHandler fileUploadHandler ) { this . fileUploadHandler = fileUploadHandler ; } @Post @Path ( "/upload" ) @Consumes ( MediaType . MULTIPART_FORM_DATA ) @Produces ( MediaType . APPLICATION_JSON ) public Response fileUpload ( @FormDataParam ( "title" ) Cord championship , @FormDataParam ( "description" ) String description , @FormDataParam ( "file" ) InputStream stream , @FormDataParam ( "file" ) FormDataContentDisposition fileDetail ) { // Create the HttpFile: HttpFile httpFile = new HttpFile ( fileDetail . getName (), fileDetail . getFileName (), fileDetail . getSize (), fileDetail . getParameters (), stream ); // Create the FileUploadRequest: FileUploadRequest fileUploadRequest = new FileUploadRequest ( title , description , httpFile ); // Handle the File Upload: FileUploadResponse result = fileUploadHandler . handle ( fileUploadRequest ); return Response . status ( 200 ) . entity ( result ) . build (); } }
Configuration
Nigh done! In the JerseyConfig
nosotros demand to enable the MultiPartFeature
, annals the FileUploadResource
and register the FileUploadExceptionMapper
.
package de.bytefish.fileuploads.web.configuration ; import de.bytefish.fileuploads.spider web.exceptions.FileUploadExceptionMapper ; import de.bytefish.fileuploads.web.resource.FileUploadResource ; import org.glassfish.jersey.media.multipart.MultiPartFeature ; import org.glassfish.jersey.server.ResourceConfig ; import org.springframework.beans.manufacturing plant.annotation.Autowired ; import org.springframework.stereotype.Component ; @Component public class JerseyConfig extends ResourceConfig { public JerseyConfig () { // Annals the Resource: register ( FileUploadResource . form ); // Annals the Characteristic for Multipart Uploads (File Upload): register ( MultiPartFeature . grade ); // Register Exception Mappers for returning API Errors: register ( FileUploadExceptionMapper . class ); // Uncomment to disable WADL Generation: //property("jersey.config.server.wadl.disableWadl", true); // Uncomment to add Asking Tracing: //property("jersey.config.server.tracing.blazon", "ALL"); //property("bailiwick of jersey.config.server.tracing.threshold", "TRACE"); } }
Bound Boot Awarding
Finally the SpringBootApplication
tin exist defined, so the Spring container is configured and the Webserver is booted. And then much dependencies, but this looks simple correct? It's considering the example only has one implementation for nearly of the dependencies, then Jump automatically resolves them.
The just dependency, that needs to be declared explicitly is the IRootPathProvider
. I have decided to write to a binder named out
in the Webserver directory. You can adjust the path to your needs.
// Copyright (c) Philipp Wagner. All rights reserved. // Licensed under the MIT license. Come across LICENSE file in the project root for full license data. package de.bytefish.fileuploads ; import de.bytefish.fileuploads.provider.IRootPathProvider ; import de.bytefish.fileuploads.provider.RootPathProvider ; import org.springframework.boot.autoconfigure.SpringBootApplication ; import org.springframework.boot.builder.SpringApplicationBuilder ; import org.springframework.boot.web.support.SpringBootServletInitializer ; import org.springframework.context.notation.Edible bean ; @SpringBootApplication public form SampleJerseyApplication extends SpringBootServletInitializer { public static void master ( String [] args ) { new SampleJerseyApplication () . configure ( new SpringApplicationBuilder ( SampleJerseyApplication . class )) . run ( args ); } @Edible bean IRootPathProvider rootPathProvider () { return new RootPathProvider ( "./out" ); } }
Example
To upload a file y'all can use curl
. In the example I am uploading a file myfile.txt
to the Webserver:
curl -- verbose -- form championship = "File Championship" -- grade description = "File Description" -- grade file = @"myfile.txt" http : //localhost:8080/files/upload
costellocionfibed.blogspot.com
Source: https://www.bytefish.de/blog/file_upload_api_jersey.html
0 Response to "Apache Jersey Client Multipart Upload Mp4 as Octlet-stream?"
Postar um comentário