SpringMVC returns images in several ways of summary

  • 2020-12-26 05:41:12
  • OfStack

The back end provides the service, which usually returns the json string, but in some scenarios, it may be necessary to directly return the base 2 stream, such as an image editing interface, and want to directly return the image stream to the front end. What can be done at this time?

I. Returns a base 2 image

Mainly with the aid of the object HttpServletResponse, case is implemented as follows


@RequestMapping(value = {"/img/render"}, method = {RequestMethod.GET, RequestMethod.POST, RequestMethod.OPTIONS})
@CrossOrigin(origins = "*")
@ResponseBody
public String execute(HttpServletRequest httpServletRequest,
       HttpServletResponse httpServletResponse) {
  // img As the picture 2 Base flow 
  byte[] img = xxx;
  httpServletResponse.setContentType("image/png");
  OutputStream os = httpServletResponse.getOutputStream();
  os.write(img);
  os.flush();
  os.close();
  return "success";
}

Matters needing attention

Note that ContentType defines the image type Write base 2 to httpServletResponse#getOutputStream After writing, flush(), close() be sure to do it once

II. Return images in several ways of encapsulation

Generally speaking, a service interface provided by a back end usually returns the majority of json data. As mentioned above, the scenario of directly returning an image. So what are the common ways to return an image?

Returns the http address of the image Returns an image in base64 format Returns the base 2 image directly The other... (I've only seen 3 of them, but I don't know anything else.)

So how can the Controller we provide support the above three gestures at the same time?

1. bean definition

Since there are several different return methods, it is of course up to the front end to specify which one to choose, so you can define an bean object with one request parameter


@Data
public class BaseRequest {
  private static final long serialVersionUID = 1146303518394712013L;
  /**
   *  Output image mode :
   *
   * url : http address   (Default mode) 
   * base64 : base64 coding 
   * stream :  Go straight back to the picture 
   *
   */
  private String outType;
  /**
   *  Returns the type of image 
   * jpg | png | webp | gif
   */ 
  private String mediaType;
  public ReturnTypeEnum returnType() {
    return ReturnTypeEnum.getEnum(outType);
  }
  public MediaTypeEnum mediaType() {
    return MediaTypeEnum.getEnum(mediaType);
  }
}

To simplify the judgment, two annotations, 1 ReturnTypeEnum and 1 MediaTypeEnum, are defined, not necessarily too much, but the following is the definition of both


public enum ReturnTypeEnum {
  URL("url"),
  STREAM("stream"),
  BASE64("base");

  private String type;
  ReturnTypeEnum(String type) {
    this.type = type;
  }
  private static Map<String, ReturnTypeEnum> map;
  static {
    map = new HashMap<>(3);
    for(ReturnTypeEnum e: ReturnTypeEnum.values()) {
      map.put(e.type, e);
    }
  }

  public static ReturnTypeEnum getEnum(String type) {
    if (type == null) {
      return URL;
    }

    ReturnTypeEnum e = map.get(type.toLowerCase());
    return e == null ? URL : e;
  }
}


@Data
public enum MediaTypeEnum {
  ImageJpg("jpg", "image/jpeg", "FFD8FF"),
  ImageGif("gif", "image/gif", "47494638"),
  ImagePng("png", "image/png", "89504E47"),
  ImageWebp("webp", "image/webp", "52494646"),
  private final String ext;
  private final String mime;
  private final String magic;
  MediaTypeEnum(String ext, String mime, String magic) {
    this.ext = ext;
    this.mime = mime;
    this.magic = magic;
  }

  private static Map<String, MediaTypeEnum> map;
  static {
    map = new HashMap<>(4);
    for (MediaTypeEnum e: values()) {
      map.put(e.getExt(), e);
    }
  }

  public static MediaTypeEnum getEnum(String type) {
    if (type == null) {
      return ImageJpg;
    }
    MediaTypeEnum e = map.get(type.toLowerCase());
    return e == null ? ImageJpg : e;
  }
}

Above is the bean encapsulated by the request parameter, which returns, of course, a corresponding bean


@Data
public class BaseResponse {

  /**
   *  Returns the relative path of the image 
   */
  private String path;


  /**
   *  Return image of https format 
   */
  private String url;


  /**
   * base64 Formatted picture 
   */
  private String base;
}

Description:

In a real project environment, the request parameters and returns would certainly not be as simple as above, so you could either inherit from bean above or define your own format

2. Packaging method of return

Since the goal is clear, encapsulation is one of the clearest steps in this


protected void buildResponse(BaseRequest request,
               BaseResponse response,
               byte[] bytes) throws SelfError {
  switch (request.returnType()) {
    case URL:
      upload(bytes, response);
      break;
    case BASE64:
      base64(bytes, response);
      break;
    case STREAM:
      stream(bytes, request);
  }
}
private void upload(byte[] bytes, BaseResponse response) throws SelfError {
  try {
    //  Upload it to the picture server and replace it according to your actual situation 
    String path = UploadUtil.upload(bytes);

    if (StringUtils.isBlank(path)) { //  Upload failed 
      throw new InternalError(null);
    }

    response.setPath(path);
    response.setUrl(CdnUtil.img(path));
  } catch (IOException e) { // cdn abnormal 
    log.error("upload to cdn error! e:{}", e);
    throw new CDNUploadError(e.getMessage());
  }
}

//  return base64
private void base64(byte[] bytes, BaseResponse response) {
  String base = Base64.getEncoder().encodeToString(bytes);
  response.setBase(base);
}
//  return 2 A binary image 
private void stream(byte[] bytes, BaseRequest request) throws SelfError {
  try {
    MediaTypeEnum mediaType = request.mediaType();
    HttpServletResponse servletResponse = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    servletResponse.setContentType(mediaType.getMime());
    OutputStream os = servletResponse.getOutputStream();
    os.write(bytes);
    os.flush();
    os.close();
  } catch (Exception e) {
    log.error("general return stream img error! req: {}, e:{}", request, e);
    if (StringUtils.isNotBlank(e.getMessage())) {
      throw new InternalError(e.getMessage());
    } else {
      throw new InternalError(null);
    }
  }
}

Description:

Please ignore the above several custom exceptions, when you need to use, you can simply kill these custom exceptions; Here is a brief introduction to 1, why the use of this custom exception in the actual project, mainly has the following advantages

Combined with global exception capture (ControllerAdvie), it is very convenient and simple to use

All exceptions are handled centrally to facilitate information statistics and alarm

For example, the abnormal count is carried out at the place of system 1, and after a certain threshold is exceeded, the alarm is sent to the person in charge, so that there is no need to actively bury the abnormal case in every place where it occurs

Avoid layers of error status codes

- This is mainly for web service, usually in the returned json string, will contain the corresponding error status code, error message
- Exception case can appear anywhere, and in order to maintain the exception information, either pass the data layer by layer to controller; Or in ThreadLocal; Obviously neither method is as easy to use as throwing exceptions

There are advantages and disadvantages, of course:

Exception mode, extra performance overhead, so In custom exceptions, I overwrite the following method, not the entire stack


@Override
public synchronized Throwable fillInStackTrace() {
  return this;
}

Coding habits, some people may not like this way of using it

Related to the project

The above design is completely reflected in the open source project Quick-Media which I maintain directly. Of course, there are some differences between the above design and the above one. After all, it is more relevant to the business, so those interested can refer to it

QuickMedia: https://github.com/liuyueyi/quick-media :

BaseAction: com.hust.hui.quickmedia.web.wxapi.WxBaseAction#buildReturn


Related articles: