文件上传以及Jersey静态Web服务中的其他对象


69

我想通过上传图像和员工数据来在系统中创建员工信息。我可以使用球衣通过其他休息电话来做到这一点。但我想在一个电话会议中实现。我在下面的结构中提供。请帮我在这方面怎么做。

@POST
@Path("/upload2")
@Consumes({MediaType.MULTIPART_FORM_DATA,MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public Response uploadFileWithData(
        @FormDataParam("file") InputStream fileInputStream,
        @FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
        Employee emp) {

//..... business login

}

每当我尝试执行此操作时,Chrome邮递员都会出现错误。我的Employee json的简单结构如下。

{
    "Name": "John",
    "Age": 23,
    "Email": "john@gmail.com",
    "Adrs": {
        "DoorNo": "12-A",
        "Street": "Street-11",
        "City": "Bangalore",
        "Country": "Karnataka"
    }
}

但是,我可以通过拨打两个不同的电话来做到这一点,但是我想在一个休息电话中实现,以便我可以接收文件以及员工的实际数据。

请您在这方面提供帮助。


您能否投票/接受答案,详情请参见stackoverflow.com/help/someone-answers?谢谢
艾伦

Answers:


100

您不能有两个Content-Types(从技术上讲,这就是我们在下面做的事情,但是它们与多部分的每个部分分开,但主要类型是多部分)。这基本上就是您对方法的期望。您期望mutlipartjson一起作为主要媒体类型。该Employee数据需要多部分的一部分。所以,你可以添加@FormDataParam("emp")Employee

@FormDataParam("emp") Employee emp) { ...

这是我用来测试的课程

@Path("/multipart")
public class MultipartResource {
    
    @POST
    @Path("/upload2")
    @Consumes({MediaType.MULTIPART_FORM_DATA})
    public Response uploadFileWithData(
            @FormDataParam("file") InputStream fileInputStream,
            @FormDataParam("file") FormDataContentDisposition cdh,
            @FormDataParam("emp") Employee emp) throws Exception{
        
        Image img = ImageIO.read(fileInputStream);
        JOptionPane.showMessageDialog(null, new JLabel(new ImageIcon(img)));
        System.out.println(cdh.getName());
        System.out.println(emp);
        
        return Response.ok("Cool Tools!").build();
    } 
}

首先,我刚刚使用客户端API进行了测试,以确保它可以正常工作

@Test
public void testGetIt() throws Exception {
    
    final Client client = ClientBuilder.newBuilder()
        .register(MultiPartFeature.class)
        .build();
    WebTarget t = client.target(Main.BASE_URI).path("multipart").path("upload2");

    FileDataBodyPart filePart = new FileDataBodyPart("file", 
                                             new File("stackoverflow.png"));
    // UPDATE: just tested again, and the below code is not needed.
    // It's redundant. Using the FileDataBodyPart already sets the
    // Content-Disposition information
    filePart.setContentDisposition(
            FormDataContentDisposition.name("file")
                                    .fileName("stackoverflow.png").build());

    String empPartJson
            = "{"
            + "  \"id\": 1234,"
            + "  \"name\": \"Peeskillet\""
            + "}";

    MultiPart multipartEntity = new FormDataMultiPart()
            .field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
            .bodyPart(filePart);
          
    Response response = t.request().post(
            Entity.entity(multipartEntity, multipartEntity.getMediaType()));
    System.out.println(response.getStatus());
    System.out.println(response.readEntity(String.class));

    response.close();
}

我刚刚创建了一个简单的Employee类,id并带有andname字段进行测试。这工作得很好。它显示图像,打印内容配置,然后打印Employee对象。

我对Postman不太熟悉,所以我将测试保存到最后:-)

在此处输入图片说明

如您所见,它似乎也可以正常工作"Cool Tools"。但是,如果我们查看打印的Employee数据,就会发现它为空。这很奇怪,因为使用客户端API可以正常工作。

如果我们查看“预览”窗口,就会发现问题所在

在此处输入图片说明

主体部分没有Content-Type标题emp。您可以在客户端API中看到我明确设置了它

MultiPart multipartEntity = new FormDataMultiPart()
        .field("emp", empPartJson, MediaType.APPLICATION_JSON_TYPE)
        .bodyPart(filePart);

因此,我想这实际上只是完整答案的一部分。就像我说的,我对邮递员不熟悉,所以我不知道如何Content-Type为身体的各个部位设置。在image/png对图像进行自动为我设置的图像部分(我猜它只是由文件扩展名确定)。如果您能解决这个问题,那么应该解决问题。请,如果您知道如何执行此操作,请将其发布为答案。

请参阅下面的更新以获取解决方案


只是为了完整性...

基本配置:

依赖关系:

<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-multipart</artifactId>
    <version>${jersey2.version}</version>
</dependency>

客户端配置:

final Client client = ClientBuilder.newBuilder()
    .register(MultiPartFeature.class)
    .build();

服务器配置:

// Create JAX-RS application.
final Application application = new ResourceConfig()
    .packages("org.glassfish.jersey.examples.multipart")
    .register(MultiPartFeature.class);

更新

因此,从Postman客户端可以看到,某些客户端无法设置各个部分的Content-Type,包括浏览器,这是使用FormData(js)时的默认功能。

我们不能指望客户能找到解决办法,因此我们可以做的就是在接收数据时在反序列化之前显式设置Content-Type。例如

@POST
@Path("upload2")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndJSON(@FormDataParam("emp") FormDataBodyPart jsonPart,
                                  @FormDataParam("file") FormDataBodyPart bodyPart) { 
     jsonPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
     Employee emp = jsonPart.getValueAs(Employee.class);
}

获取POJO会花费一些额外的工作,但是比强迫客户端尝试找到自己的解决方案要好。

另一个选择是使用String参数,并使用将JSON反序列化为POJO所使用的任何JSON库(例如Jackson ObjectMapper)。使用前一个选项,我们只允许Jersey处理反序列化,它将使用与所有其他JSON端点(可能是首选)相同的JSON库。


阿西德斯

  • 如果您使用的连接器不同于默认的HttpUrlConnection,则您可能会对这些注释中的对话感兴趣。

嗨,我可以麻烦您(或任何其他懂得这一点的人)更新客户端API中的代码。例如,第一行具有“ c.target”,但是这里的c是什么?
2015年

@ user3223841请参阅底部的“客户端配置”。c == client。然后我更新了代码。谢谢。
保罗·萨姆索塔

您是否在大型文件上测试过代码?我在表单提交时遇到相同的错误
gkiko 2015年

7
如果可以的话,我给你100票。这是经过充分研究,最新,有用的解决方法,它的含义是!
cen

1
@AzamatAlmukhametov不需要使用FormDataBodyPart的文件部分。我想我只是复制和粘贴。您可以将其保留为InputStream参数。JSON部分仅需要FormDataBodyPart,因为我们需要更改媒体类型。但是对于文件,我们只能获取原始输入流。另一种方法是使用bodyPart.getValueAs(InputStream.class),但这不是必需的。只需将参数InputStream
设为

2

您可以使用MULTIPART FORM DATA通过以下代码访问表单中的图像文件和数据。

@POST
@Path("/UpdateProfile")
@Consumes(value={MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
@Produces(value={MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML})
public Response updateProfile(
    @FormDataParam("file") InputStream fileInputStream,
    @FormDataParam("file") FormDataContentDisposition contentDispositionHeader,
    @FormDataParam("ProfileInfo") String ProfileInfo,
    @FormDataParam("registrationId") String registrationId) {

    String filePath= "/filepath/"+contentDispositionHeader.getFileName();

    OutputStream outputStream = null;
    try {
        int read = 0;
        byte[] bytes = new byte[1024];
        outputStream = new FileOutputStream(new File(filePath));

        while ((read = fileInputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, read);
        }

        outputStream.flush();
        outputStream.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (outputStream != null) { 
            try {
                outputStream.close();
            } catch(Exception ex) {}
        }
    }
}

1

当我在Jersey客户端2.21.1上尝试@PaulSamsotha的解决方案时,出现400错误。当我在客户端代码中添加以下代码时,它起作用了:

MediaType contentType = MediaType.MULTIPART_FORM_DATA_TYPE;
contentType = Boundary.addBoundary(contentType);

Response response = t.request()
        .post(Entity.entity(multipartEntity, contentType));

而不是MediaType.MULTIPART_FORM_DATA在POST请求调用中进行硬编码。

之所以需要这样做,是因为当您为Jersey客户端使用其他连接器(如Apache)时,它无法更改出站标头,而这是向Content-Type添加边界所必需的。Jersey Client文档中对此限制进行了说明。因此,如果要使用其他连接器,则需要手动创建边界。


您无需手动创建边界。您可以使用multipartEntity.getMediaType()。我最初使用的MediaType.MULTIPART_FORM_DATA是没有添加边界。但是当你使用getMediaType()的方法MutliPart,这有边界。
Paul Samsotha

@PaulSamsotha如果使用其他连接器(即apache而不是jdk),则不会为您生成边界。

@本你是对的。在文档中的某个地方,它针对不同的连接器进行了说明,其中WriterInterceptors和MessageBodyWriters无法更改出站标头。这是添加边界所必需的。请参阅客户端文档中的警告
Paul Samsotha

1
因此,要么自动生成边界,但不分块上传(在这方面HttpUrlConnection是伪造的),要么手动生成边界。我选择后者,是因为在不警告用户的情况下缓冲4 GiB文件是不行的。我在他们的github跟踪器上创建了一个问题。


0

您的ApplicationConfig应该从glassfish.jersey.media ..注册MultiPartFeature.class以便启用文件上传

@javax.ws.rs.ApplicationPath(ResourcePath.API_ROOT)
public class ApplicationConfig extends ResourceConfig {  
public ApplicationConfig() {
        //register the necessary headers files needed from client
        register(CORSConfigurationFilter.class);
        //The jackson feature and provider is used for object serialization
        //between client and server objects in to a json
        register(JacksonFeature.class);
        register(JacksonProvider.class);
        //Glassfish multipart file uploader feature
        register(MultiPartFeature.class);
        //inject and registered all resources class using the package
        //not to be tempered with
        packages("com.flexisaf.safhrms.client.resources");
        register(RESTRequestFilter.class);
    }

0

请求的类型是multipart / form-data,而您要发送的本质上是形式为字节的形式形式的字段,内容边界将不同的形式字段分开。要以形式字段(字符串)形式发送对象表示形式,可以从然后可以在服务器上反序列化的客户端。

毕竟,没有编程环境对象实际上是在线上传播的。双方的编程环境都在执行自动序列化和反序列化,您也可以这样做。这是最干净的编程环境,它是免费的方法。

举例来说,这是一个JavaScript客户端发布到Jersey示例服务,

submitFile(){

    let data = new FormData();
    let account = {
        "name": "test account",
        "location": "Bangalore"
    }

    data.append('file', this.file);
    data.append("accountKey", "44c85e59-afed-4fb2-884d-b3d85b051c44");
    data.append("device", "test001");
    data.append("account", JSON.stringify(account));
    
    let url = "http://localhost:9090/sensordb/test/file/multipart/upload";

    let config = {
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    }

    axios.post(url, data, config).then(function(data){
        console.log('SUCCESS!!');
        console.log(data.data);
    }).catch(function(){
        console.log('FAILURE!!');
    });
},

客户端在这里发送一个文件,2个表单字段(字符串)和一个已经过字符串传输的帐户对象。这是表单字段在网上的外观,

在此处输入图片说明

在服务器上,您可以按照自己认为合适的方式反序列化表单字段。为了完成这个简单的例子,

    @POST
@Path("/file/multipart/upload")
@Consumes({MediaType.MULTIPART_FORM_DATA})
public Response uploadMultiPart(@Context ContainerRequestContext requestContext,
        @FormDataParam("file") InputStream fileInputStream,
        @FormDataParam("file") FormDataContentDisposition cdh,
        @FormDataParam("accountKey") String accountKey,
        @FormDataParam("account") String json)  {
    

    
    System.out.println(cdh.getFileName());
    System.out.println(cdh.getName());
    System.out.println(accountKey);
    
    try {
        Account account = Account.deserialize(json);
        System.out.println(account.getLocation());
        System.out.println(account.getName());
        
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    return Response.ok().build();
    
}

-1

我使用了来自的文件上传示例

http://www.mkyong.com/webservices/jax-rs/file-upload-example-in-jersey/

在我的资源类中,我有以下方法

@POST
    @Path("/upload")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public Response  attachupload(@FormDataParam("file") byte[] is,
@FormDataParam("file") FormDataContentDisposition fileDetail,
@FormDataParam("fileName") String flename){
attachService.saveAttachment(flename,is);
}

在我的attachService.java中,我有以下方法

 public void saveAttachment(String flename,  byte[] is) {
            // TODO Auto-generated method stub
         attachmentDao.saveAttachment(flename,is);

        }

在道我有

attach.setData(is);
attach.setFileName(flename);

在我的HBM映射中就像

<property name="data" type="binary" >
            <column name="data" />
</property>

适用于所有类型的文件,例如.PDF,.TXT,.PNG等,


您只需使用byte []参数而不是InputStream节省了我的生活... @Ramam我欠您一杯啤酒!
马里奥·卡瓦略
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.