简体   繁体   中英

How to handle org.springframework.core.io.Resource for Swagger Codegen

I am developing a web application using Spring Boot 2, which have 2 Spring projects working together: one is the REST API server serving the application logic via REST APIs, the other is the web project responsible for rendering web pages and calling the API server. The web project uses Swagger Codegen to auto generate classes for calling the APIs.

In the API server, I have a controller ResourceController with an endpoint for serving a file content (ie download a file), as follows

@GetMapping("/files/{uuid}")
@ResponseBody
public org.springframework.core.io.Resource getFile(@PathVariable String uuid) {
    String systemPath = fileService.getFilePath(uuid);
    return new FileSystemResource(systemPath);
}

At the web client, Swagger generate ResourceControllerApi with the method translated to

public io.swagger.client.model.Resource getFileUsingGET(String uuid) {...}

I want to create a controller at the web project that pass through the request and response between the user's browser to the API server. I tried

@GetMapping("/client/files/{uuid}")
@ResponseBody
public io.swagger.client.model.Resource getFile(@PathVariable String uuid) {
    return resourceControllerApi.getFileUsingGET(uuid);
}

When calling the API (at client web), I got this error

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class io.swagger.client.model.Resource] and content type [image/jpeg]

I expect that when I put the url like http://myweb/client/files/dee38be4-6ef9-460d-bc44-f1b93770ab83 , the browser download the file content. I have been looking a way to converting io.swagger.client.model.Resource to org.springframework.core.io.Resource but could not figure out.

Following is the content of the auto generated io.swagger.client.model.Resource

/*
 * NPA Marketplace REST API
 * API to manage NPA Marketplace.
 *
 * OpenAPI spec version: 1.0
 * 
 *
 * NOTE: This class is auto generated by the swagger code generator program.
 * https://github.com/swagger-api/swagger-codegen.git
 * Do not edit the class manually.
 */


package io.swagger.client.model;

import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import io.swagger.client.model.InputStream;
import io.swagger.client.model.URI;
import io.swagger.client.model.URL;

/**
 * Resource
 */
@javax.annotation.Generated(value = "io.swagger.codegen.languages.JavaClientCodegen", date = "2018-03-08T14:55:08.754+07:00")
public class Resource {
  @JsonProperty("description")
  private String description = null;

  @JsonProperty("file")
  private java.io.File file = null;

  @JsonProperty("filename")
  private String filename = null;

  @JsonProperty("inputStream")
  private InputStream inputStream = null;

  @JsonProperty("open")
  private Boolean open = null;

  @JsonProperty("readable")
  private Boolean readable = null;

  @JsonProperty("uri")
  private URI uri = null;

  @JsonProperty("url")
  private URL url = null;

  public Resource description(String description) {
    this.description = description;
    return this;
  }

   /**
   * Get description
   * @return description
  **/
  @ApiModelProperty(value = "")
  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }

  public Resource file(java.io.File file) {
    this.file = file;
    return this;
  }

   /**
   * Get file
   * @return file
  **/
  @ApiModelProperty(value = "")
  public java.io.File getFile() {
    return file;
  }

  public void setFile(java.io.File file) {
    this.file = file;
  }

  public Resource filename(String filename) {
    this.filename = filename;
    return this;
  }

   /**
   * Get filename
   * @return filename
  **/
  @ApiModelProperty(value = "")
  public String getFilename() {
    return filename;
  }

  public void setFilename(String filename) {
    this.filename = filename;
  }

  public Resource inputStream(InputStream inputStream) {
    this.inputStream = inputStream;
    return this;
  }

   /**
   * Get inputStream
   * @return inputStream
  **/
  @ApiModelProperty(value = "")
  public InputStream getInputStream() {
    return inputStream;
  }

  public void setInputStream(InputStream inputStream) {
    this.inputStream = inputStream;
  }

  public Resource open(Boolean open) {
    this.open = open;
    return this;
  }

   /**
   * Get open
   * @return open
  **/
  @ApiModelProperty(value = "")
  public Boolean isOpen() {
    return open;
  }

  public void setOpen(Boolean open) {
    this.open = open;
  }

  public Resource readable(Boolean readable) {
    this.readable = readable;
    return this;
  }

   /**
   * Get readable
   * @return readable
  **/
  @ApiModelProperty(value = "")
  public Boolean isReadable() {
    return readable;
  }

  public void setReadable(Boolean readable) {
    this.readable = readable;
  }

  public Resource uri(URI uri) {
    this.uri = uri;
    return this;
  }

   /**
   * Get uri
   * @return uri
  **/
  @ApiModelProperty(value = "")
  public URI getUri() {
    return uri;
  }

  public void setUri(URI uri) {
    this.uri = uri;
  }

  public Resource url(URL url) {
    this.url = url;
    return this;
  }

   /**
   * Get url
   * @return url
  **/
  @ApiModelProperty(value = "")
  public URL getUrl() {
    return url;
  }

  public void setUrl(URL url) {
    this.url = url;
  }


  @Override
  public boolean equals(java.lang.Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Resource resource = (Resource) o;
    return Objects.equals(this.description, resource.description) &&
        Objects.equals(this.file, resource.file) &&
        Objects.equals(this.filename, resource.filename) &&
        Objects.equals(this.inputStream, resource.inputStream) &&
        Objects.equals(this.open, resource.open) &&
        Objects.equals(this.readable, resource.readable) &&
        Objects.equals(this.uri, resource.uri) &&
        Objects.equals(this.url, resource.url);
  }

  @Override
  public int hashCode() {
    return Objects.hash(description, file, filename, inputStream, open, readable, uri, url);
  }


  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class Resource {\n");

    sb.append("    description: ").append(toIndentedString(description)).append("\n");
    sb.append("    file: ").append(toIndentedString(file)).append("\n");
    sb.append("    filename: ").append(toIndentedString(filename)).append("\n");
    sb.append("    inputStream: ").append(toIndentedString(inputStream)).append("\n");
    sb.append("    open: ").append(toIndentedString(open)).append("\n");
    sb.append("    readable: ").append(toIndentedString(readable)).append("\n");
    sb.append("    uri: ").append(toIndentedString(uri)).append("\n");
    sb.append("    url: ").append(toIndentedString(url)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(java.lang.Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }

}

The error

org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class io.swagger.client.model.Resource] and content type [image/jpeg] 

was emitted from the line

ResponseEntity<T> responseEntity = restTemplate.exchange(requestEntity, returnType);

in the class io.swagger.client.ApiClient which is auto-generated by Swagger Codegen of variant library resttemplate , ie equivalent to running command java -jar swagger-codegen-cli.jar generate -i api-specs.json -l java --library resttemplate

The error indicates that the resttemplate does not know how to convert the response data to io.swagger.client.model.Resource and hint us to create the suitable HttpMessageConverter . This is a tutorial for creating a HttpMessageConverters and register them with the resttemplate .

In my case, however, the HttpMessageConverter cannot be a solution because the generated io.swagger.client.model.Resource has no property to keep the file data from the API's response. It seems Swagger Codegen misunderstands that the controller at the API server would return a JSON representation of org.springframework.core.io.Resource and thus generating the counter part io.swagger.client.model.Resource for receiving the JSON data at the client, but the API server actually responds with a stream of file data, not a JSON.

I don't know if this can be a bug for Swagger Codegen or I have done it wrong some how.

Nevertheless, I decide to give up using org.springframework.core.io.Resource at the API server. Instead, I change the return type of the controller to ResponseEntity<byte[]> and manually configure the response header to have the correct content type and file name, as follows

ResourceController (API Server)

@GetMapping("/files/{uuid}")
@ResponseBody
public ResponseEntity<byte[]> getFile(@PathVariable String uuid) {
    FileMetadata myFileData = fileService.getFileMetadata(uuid);
    org.springframework.core.io.Resource res = new FileSystemResource(myFileData.getPaht());
    byte[] data = IOUtils.toByteArray(res.getInputStream());
    HttpHeaders respHeaders = new HttpHeaders();
    respHeaders.setContentType(MediaType.valueOf(myFileData.getContentType()));
    respHeaders.setContentLength(res.getFile().length());
    respHeaders.setContentDispositionFormData("attachment", myFileData.getName());
    return new ResponseEntity<>(data ,respHeaders, HttpStatus.OK);
}

At the web client, I then convert the byte[] data received from the API server to org.springframework.core.io.Resource , passing through the response header from the API server.

A controller at web client server

@GetMapping("/client/files/{uuid}")
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> getFile(@PathVariable String uuid) {
    byte[] data = resourceControllerApi.getFileUsingGET(uuid);
    HttpHeaders responseHeader = (HttpHeaders) RequestContextHolder.getRequestAttributes().getAttribute("responseHeader", RequestAttributes.SCOPE_REQUEST);
    return new ResponseEntity<>(new ByteArrayResource(data), responseHeader, HttpStatus.OK);
}

About the way I use to retrieve the response header, since I have no way to get the response header from the generated api class, I have to created a ClientHttpRequestInterceptor to get the header and put it as a request attribute.

public class ResponseHeaderClientRequestInterceptor implements ClientHttpRequestInterceptor {
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);
        HttpHeaders responseHeader = response.getHeaders();
        RequestAttributes requestAttrs = RequestContextHolder.getRequestAttributes();
        if (requestAttrs != null) {
            requestAttrs.setAttribute("responseHeader", responseHeader, RequestAttributes.SCOPE_REQUEST);
        }
        return response;
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM