spring-cloud-gateway icon indicating copy to clipboard operation
spring-cloud-gateway copied to clipboard

Spring Cloud Gateway MVC : Multipart + Query Param is not working

Open khannedy opened this issue 1 year ago • 3 comments

Describe the bug Send multipart request and also query parameter, the query parameter is not forwarded to backend service

Spring Cloud Version : 2023.0.3

Sample

spring.cloud.gateway.mvc.routes[0].id=NOCHECK
spring.cloud.gateway.mvc.routes[0].uri=https://engvxhu8p8n2b.x.pipedream.net
spring.cloud.gateway.mvc.routes[0].predicates[0]=Path=/backend/nocheck/**

Example Curl :

curl --location 'http://localhost:8089/backend/nocheck/eko?orderId=27006607934&storeId=10001&clientId=off2on-mobile&channelId=off2on-mobile&requestId=7bcf27c6-ef84-4715-9a9b-a77ff77ddb43&username=b80edb9d-790e-4563-aaf8-a61dd128a653%40blibli' \
--header 'Content-Type: multipart/form-data' \
--form 'name="Eko"' \
--form 'hello="test"'

Backend Received :

CleanShot 2024-07-31 at 14 56 29@2x

khannedy avatar Jul 31 '24 07:07 khannedy

current solution, is to change MultipartResolver from GatewayMvcMultipartResolver to custom resolver

package com.gdn.rnd.backend.gateway.external.filter.multipart;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.util.UriComponentsBuilder;

import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * This class is a custom class because {@link org.springframework.cloud.gateway.server.mvc.handler.GatewayMvcMultipartResolver}
 * still has bug on query parameter
 *
 * @see org.springframework.cloud.gateway.server.mvc.handler.GatewayMvcMultipartResolver
 */
@Component
public class BlibliMultipartResolver extends StandardServletMultipartResolver {

  @Override
  public boolean isMultipart(HttpServletRequest request) {
    return super.isMultipart(request);
  }

  @Override
  public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
    String queryString = request.getQueryString();
    StringBuffer requestURL = request.getRequestURL();
    if (StringUtils.hasText(queryString)) {
      requestURL.append('?').append(queryString);
    }
    UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(requestURL.toString());
    MultiValueMap<String, String> queryParams = uriComponentsBuilder.build().getQueryParams();

    return new BlibliMultipartResolver.GatewayMultipartHttpServletRequest(request, queryParams);
  }

  private static boolean isGatewayRequest(HttpServletRequest request) {
    return request.getAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR) != null
      || request.getAttribute(MvcUtils.GATEWAY_REQUEST_URL_ATTR) != null;
  }

  /**
   * StandardMultipartHttpServletRequest wrapper that will not parse multipart if it is
   * a gateway request. A gateway request has certain request attributes set.
   */
  static class GatewayMultipartHttpServletRequest extends StandardMultipartHttpServletRequest {

    private final MultiValueMap<String, String> queryParams;

    GatewayMultipartHttpServletRequest(HttpServletRequest request, MultiValueMap<String, String> queryParams) {
      super(request, true);
      this.queryParams = queryParams;
    }

    @Override
    protected void initializeMultipart() {
      if (!isGatewayRequest(getRequest())) {
        super.initializeMultipart();
      }
    }

    @Override
    public Map<String, String[]> getParameterMap() {
      if (isGatewayRequest(getRequest())) {
        return getQueryParameterMap();
      }
      return super.getParameterMap();
    }

    private Map<String, String[]> getQueryParameterMap() {
      Map<String, String[]> result = new LinkedHashMap<>();
      Enumeration<String> names = getParameterNames();
      while (names.hasMoreElements()) {
        String name = names.nextElement();
        result.put(name, getParameterValues(name));
      }
      return result;
    }

    @Override
    public String getParameter(String name) {
      return this.queryParams.getFirst(name);
    }

    @Override
    public Enumeration<String> getParameterNames() {
      return Collections.enumeration(this.queryParams.keySet());
    }

    @Override
    public String[] getParameterValues(String name) {
      return StringUtils.toStringArray(this.queryParams.get(name));
    }

  }
}

khannedy avatar Jul 31 '24 10:07 khannedy

i have the same issue. This problem appears to be because getParameterMap Method of GatewayMultipartHttpServletRequest is return emptyMap. i replaced the MultipartResolver Bean of Gateway MultipartAutoConfiguration temporarily Like the solution given by khannedy

leaf-upper avatar Aug 05 '24 06:08 leaf-upper

Duplicate of https://github.com/spring-cloud/spring-cloud-gateway/issues/3220

rworsnop avatar Aug 12 '24 14:08 rworsnop