相关文章推荐
乖乖的绿豆  ·  五星推荐PyTorch ...·  1 年前    · 
想旅行的香烟  ·  YoloX - 知乎·  1 年前    · 

Jdk 版本 1.8

2. 报错1:

java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

这个问题出现在较高版本的Tomcat上,URL需按照 RFC 3986规范进行访问解析,而 RFC 3986规范定义了Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符(RFC3986中指定了以下字符为保留字符:! * ’ ( ) ; : @ & = + $ , / ? # [ ])。

2.1 尝试

一般的Springboot项目碰到问题可以通过加入以下代码可以解决,不过在Spring gateway上并没有生效

	@Bean
	public ConfigurableServletWebServerFactory webServerFactory() {
		TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
		factory.addConnectorCustomizers(connector -> {
            // 一般只配置第一个即可,高版本可能还需要后边几个
            connector.setProperty("relaxedQueryChars", "|");
            connector.setProperty("relaxedPathChars", "|");
            connector.setProperty("requestTargetAllow","|");
            connector.setProperty("rejectIllegalHeader", "false");
		return factory;

此外也尝试了增加VM环境变量,也没有用

-Dtomcat.util.http.parser.HttpParser.requestTargetAllow=|{}
//下载main方法中
System.setProperty("tomcat.util.http.parser.HttpParser.requestTargetAllow","|{}");

2.2 解决方案

在项目yml文件增加配置项

 server:
  tomcat:
    relaxed-query-chars: "|"
    # 下边几个自行选用,一般是不需要的
    #    relaxed-path-chars: "|"
    #    request-target-allow: "|"
    #    reject-illegal-header: false

注:requestTargetAllow在tomcat8.5已弃用

3. 报错2:

通过上述配置就不会报非法字符的问题,但是调试之后程序并没有返回数据,也没有任何状态码。

将程序Log等级调为debug

logging:
  level:
    root: DEBUG
[p-nio-80-exec-2] o.s.h.s.r.ServletHttpHandlerAdapter: Failed to get request  URL: Illegal character in query at index xxx......

排查发现问题出现在adapter初始化URL时,不能转换特殊字符,所以需要覆写源码针对特殊字符进行URL转码即可。

关键代码:

private static URI initUri(HttpServletRequest request) throws URISyntaxException {
        Assert.notNull(request, "'request' must not be null");
        StringBuffer url = request.getRequestURL();
        String query = request.getQueryString();
        if (StringUtils.hasText(query)) {
          //改动URL,对特殊字符进行编码
            if(query.contains("|")){
              query = query.replace("|", "%7C");
            url.append('?').append(query);
    	//问题出现在这里
        return new URI(url.toString());
 public URI(String str) throws URISyntaxException {
        new Parser(str).parse(false);

完整代码如下

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * https://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. package org.springframework.http.server.reactive; import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.security.cert.X509Certificate; import java.util.Enumeration; import java.util.Locale; import java.util.Map; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import reactor.core.publisher.Flux; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; * @Description: 覆盖源码,支持特殊字符 class ServletServerHttpRequest extends AbstractServerHttpRequest { static final DataBuffer EOF_BUFFER = DefaultDataBufferFactory.sharedInstance.allocateBuffer(0); private final HttpServletRequest request; private final RequestBodyPublisher bodyPublisher; private final Object cookieLock = new Object(); private final DataBufferFactory bufferFactory; private final byte[] buffer; private final AsyncListener asyncListener; public ServletServerHttpRequest(HttpServletRequest request, AsyncContext asyncContext, String servletPath, DataBufferFactory bufferFactory, int bufferSize) throws IOException, URISyntaxException { this(createDefaultHttpHeaders(request), request, asyncContext, servletPath, bufferFactory, bufferSize); public ServletServerHttpRequest(MultiValueMap<String, String> headers, HttpServletRequest request, AsyncContext asyncContext, String servletPath, DataBufferFactory bufferFactory, int bufferSize) throws IOException, URISyntaxException { super(initUri(request), request.getContextPath() + servletPath, initHeaders(headers, request)); Assert.notNull(bufferFactory, "'bufferFactory' must not be null"); Assert.isTrue(bufferSize > 0, "'bufferSize' must be higher than 0"); this.request = request; this.bufferFactory = bufferFactory; this.buffer = new byte[bufferSize]; this.asyncListener = new RequestAsyncListener(); // Tomcat expects ReadListener registration on initial thread ServletInputStream inputStream = request.getInputStream(); this.bodyPublisher = new RequestBodyPublisher(inputStream); this.bodyPublisher.registerReadListener(); private static MultiValueMap<String, String> createDefaultHttpHeaders(HttpServletRequest request) { MultiValueMap<String, String> headers = CollectionUtils.toMultiValueMap(new LinkedCaseInsensitiveMap<>(8, Locale.ENGLISH)); for (Enumeration<?> names = request.getHeaderNames(); names.hasMoreElements(); ) { String name = (String) names.nextElement(); for (Enumeration<?> values = request.getHeaders(name); values.hasMoreElements(); ) { headers.add(name, (String) values.nextElement()); return headers; private static URI initUri(HttpServletRequest request) throws URISyntaxException { Assert.notNull(request, "'request' must not be null"); StringBuffer url = request.getRequestURL(); String query = request.getQueryString(); //改动URL,对特殊字符进行编码 if(query.contains("|")){ query = query.replace("|", "%7C"); if (StringUtils.hasText(query)) { url.append('?').append(query); return new URI(url.toString()); private static MultiValueMap<String, String> initHeaders( MultiValueMap<String, String> headerValues, HttpServletRequest request) { HttpHeaders headers = null; MediaType contentType = null; if (!StringUtils.hasLength(headerValues.getFirst(HttpHeaders.CONTENT_TYPE))) { String requestContentType = request.getContentType(); if (StringUtils.hasLength(requestContentType)) { contentType = MediaType.parseMediaType(requestContentType); headers = new HttpHeaders(headerValues); headers.setContentType(contentType); if (contentType != null && contentType.getCharset() == null) { String encoding = request.getCharacterEncoding(); if (StringUtils.hasLength(encoding)) { Map<String, String> params = new LinkedCaseInsensitiveMap<>(); params.putAll(contentType.getParameters()); params.put("charset", Charset.forName(encoding).toString()); headers.setContentType(new MediaType(contentType, params)); if (headerValues.getFirst(HttpHeaders.CONTENT_TYPE) == null) { int contentLength = request.getContentLength(); if (contentLength != -1) { headers = (headers != null ? headers : new HttpHeaders(headerValues)); headers.setContentLength(contentLength); return (headers != null ? headers : headerValues); @Override public String getMethodValue() { return this.request.getMethod(); @Override protected MultiValueMap<String, HttpCookie> initCookies() { MultiValueMap<String, HttpCookie> httpCookies = new LinkedMultiValueMap<>(); Cookie[] cookies; synchronized (this.cookieLock) { cookies = this.request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { String name = cookie.getName(); HttpCookie httpCookie = new HttpCookie(name, cookie.getValue()); httpCookies.add(name, httpCookie); return httpCookies; @Override @NonNull public InetSocketAddress getLocalAddress() { return new InetSocketAddress(this.request.getLocalAddr(), this.request.getLocalPort()); @Override @NonNull public InetSocketAddress getRemoteAddress() { return new InetSocketAddress(this.request.getRemoteHost(), this.request.getRemotePort()); @Override @Nullable protected SslInfo initSslInfo() { X509Certificate[] certificates = getX509Certificates(); return certificates != null ? new DefaultSslInfo(getSslSessionId(), certificates) : null; @Nullable private String getSslSessionId() { return (String) this.request.getAttribute("javax.servlet.request.ssl_session_id"); @Nullable private X509Certificate[] getX509Certificates() { String name = "javax.servlet.request.X509Certificate"; return (X509Certificate[]) this.request.getAttribute(name); @Override public Flux<DataBuffer> getBody() { return Flux.from(this.bodyPublisher); @SuppressWarnings("unchecked") @Override public <T> T getNativeRequest() { return (T) this.request; * Return an {@link RequestAsyncListener} that completes the request body * Publisher when the Servlet container notifies that request input has ended. * The listener is not actually registered but is rather exposed for * {@link ServletHttpHandlerAdapter} to ensure events are delegated. AsyncListener getAsyncListener() { return this.asyncListener; * Read from the request body InputStream and return a DataBuffer. * Invoked only when {@link ServletInputStream#isReady()} returns "true". * @return a DataBuffer with data read, or {@link #EOF_BUFFER} if the input * stream returned -1, or null if 0 bytes were read. @Nullable DataBuffer readFromInputStream() throws IOException { int read = this.request.getInputStream().read(this.buffer); logBytesRead(read); if (read > 0) { DataBuffer dataBuffer = this.bufferFactory.allocateBuffer(read); dataBuffer.write(this.buffer, 0, read); return dataBuffer; if (read == -1) { return EOF_BUFFER; return null; protected final void logBytesRead(int read) { Log rsReadLogger = AbstractListenerReadPublisher.rsReadLogger; if (rsReadLogger.isTraceEnabled()) { rsReadLogger.trace(getLogPrefix() + "Read " + read + (read != -1 ? " bytes" : "")); private final class RequestAsyncListener implements AsyncListener { @Override public void onStartAsync(AsyncEvent event) { @Override public void onTimeout(AsyncEvent event) { Throwable ex = event.getThrowable(); ex = ex != null ? ex : new IllegalStateException("Async operation timeout."); bodyPublisher.onError(ex); @Override public void onError(AsyncEvent event) { bodyPublisher.onError(event.getThrowable()); @Override public void onComplete(AsyncEvent event) { bodyPublisher.onAllDataRead(); private class RequestBodyPublisher extends AbstractListenerReadPublisher<DataBuffer> { private final ServletInputStream inputStream; public RequestBodyPublisher(ServletInputStream inputStream) { super(ServletServerHttpRequest.this.getLogPrefix()); this.inputStream = inputStream; public void registerReadListener() throws IOException { this.inputStream.setReadListener(new RequestBodyPublisherReadListener()); @Override protected void checkOnDataAvailable() { if (this.inputStream.isReady() && !this.inputStream.isFinished()) { onDataAvailable(); @Override @Nullable protected DataBuffer read() throws IOException { if (this.inputStream.isReady()) { DataBuffer dataBuffer = readFromInputStream(); if (dataBuffer == EOF_BUFFER) { // No need to wait for container callback... onAllDataRead(); dataBuffer = null; return dataBuffer; return null; @Override protected void readingPaused() { // no-op @Override protected void discardData() { // Nothing to discard since we pass data buffers on immediately.. private class RequestBodyPublisherReadListener implements ReadListener { @Override public void onDataAvailable() throws IOException { RequestBodyPublisher.this.onDataAvailable(); @Override public void onAllDataRead() throws IOException { RequestBodyPublisher.this.onAllDataRead(); @Override public void onError(Throwable throwable) { RequestBodyPublisher.this.onError(throwable);
  • SpringCloud + SpringGateway 解决Get请求传参为特殊字符导致400无法通过网关转发的问题
  • 基于springCloud gateway请求包含url包含{}大括号特殊字符的问题_嗜湮的博客-CSDN博客_gateway 特殊字符
  • 微服务模式下url中带{}[\]等特殊字符时请求异常返回400_url {}_Yang、倾听的博客-CSDN博客
  • Spring Cloud Gateway 和Webflux 请求参数非法字符处理_Java_a1vin-tian_InfoQ写作社区
  • The valid characters are defined in RFC 7230 and RFC 3986
  •