This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

GetXxByIds (String Ids), whose Ids are separated by ‘,’, runs for a while and returns 400.

Content of the error

feign.FeignException$BadRequest: [400] during [POST] to [http...
Copy the code

Problem reproduction

Let’s simulate the problem

The service interface

@Slf4j
@RestController
public class SkyController {

  @GetMapping("/sky")
  public String sky (@RequestParam(required = false) String name) {
    log.info(name);
    return name;
  }

  @PostMapping("/deliver")
  public String deliver (String packageBox) {
    log.info(packageBox);
    return packageBox;
  }


  @PostMapping("/feignPost")
  public String feignPost(@RequestParam(name = "name") String name){
    log.info(name);
    return name;
  }


  @PostMapping("/feignBody")
  public String feignBody(@RequestBody String name){
    log.info(name);
    returnname; }}Copy the code

Create another service to create feignClinet. The sample service adds the gateway, uses the service name, and can use the URL directly

// Gateway service name
@FeignClient(value = "paw-dogs-sky-service")
public interface SkyFeignClient {

  @PostMapping("/deliver")
  String deliver (@RequestParam(name = "packageBox") String packageBox);

  @PostMapping("/feignPost")
  String feignPost(@RequestParam(name = "name") String name);

  @PostMapping("/feignBody")
  String feignBody(@RequestBody String name);
}
Copy the code

Writing test classes

@Slf4j
@SpringBootTest
class SkyFeignClientTest {


  @Autowired
  SkyFeignClient skyFeignClient;

  @Test
  void deliver (a) {
    String param = RandomUtil.randomString(10*1024);
    log.info(param);
    String result = skyFeignClient.deliver(param);
    log.info(result);

  }

  @Test
  void feignPost (a) {
    String param = RandomUtil.randomString(10*1024);
    log.info(param);
    String result = skyFeignClient.feignPost(param);
    log.info(result);
  }

  @Test
  void feignBody (a) {
    String param = RandomUtil.randomString(1*1024); log.info(param); String result = skyFeignClient.feignBody(param); log.info(result); }}Copy the code

When the parAM is large, the get and POST form-URL requests fail, and the requestBody requests succeed.

Run the POST form-URL request and you will find that the POST request is also sent in the form of url concatenation

Access the service tests directly with Postman

Failed to access a concatenated long URL

The access succeeded in form-URL mode. Procedure

The problem arises in the length limit of the URL

Url length limit

  1. Browser URL length limit

  2. Server length restrictions such as Tomcat restrictions, nginx URL restrictions

    Maximum SpringBoot project length max-http-header-size the default value is 8k. Change the configuration of the sky service to 100 x 1024 (100k) and retry the normal service invocation.

    DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8)

    server:
      port: 8080
      max-http-header-size: 102400
    Copy the code

Source code analysis

Start the Debug trace with the exception class SynchronousMethodHandler

The RequestTemplate class that builds the request contains the ordered list Map

queries = new LinkedHashMap<>()

SpringMvcContract handles the annotations

Annotation processAnnotationOnClass on the class

Methods the annotations on processAnnotationOnMethod

The parameters of the annotations on processAnnotationsOnParameter

The implementation class AnnotatedParameterProcessor RequestParamParameterProcessor to encapsulate the query parameters

public boolean processArgument(AnnotatedParameterContext context, Annotation annotation, Method method) {
		intparameterIndex = context.getParameterIndex(); Class<? > parameterType = method.getParameterTypes()[parameterIndex]; MethodMetadata data = context.getMethodMetadata();if (Map.class.isAssignableFrom(parameterType)) {
			checkState(data.queryMapIndex() == null."Query map can only be present once.");
			data.queryMapIndex(parameterIndex);

			return true; } RequestParam requestParam = ANNOTATION.cast(annotation); String name = requestParam.value(); checkState(emptyToNull(name) ! =null."RequestParam.value() was empty on parameter %s", parameterIndex);
		context.setParameterName(name);

  	// Encapsulate the parameters
		Collection<String> query = context.setTemplateParameter(name,
				data.template().queries().get(name));
		data.template().query(name, query);
		return true;
	}
Copy the code

SynchronousMethodHandler Invoke method executeAndDecode(Template, Options) executes the HTTP request and decodes it.

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {
  // Build the request
  Request request = targetRequest(template);

  if(logLevel ! = Logger.Level.NONE) { logger.logRequest(metadata.configKey(), logLevel, request); } Response response;long start = System.nanoTime();
  try {
    // Execute the HTTP requestresponse = client.execute(request, options); . }Copy the code

Construct the Request Request to access the HTTP service through the client. Implementation of the client have a Default LoadBalancerFeignClient FeignBlockingLoadBalancerClient

To solve

1. Increase the header parameter of the service provider (not applicable for many micro-services) 2. Instead, requestBody invokes the serviceCopy the code

conclusion

Feign constructs a Request through Url concatenation by parsing annotations on interface classes, methods, and parameters, and accessing HTTP services through the Client through RequestTemplate @RequestParam. Call the service in RequestBody instead of longer parameters.