• 背景

微服务架构:springboot +springboot security+springboot oauth2 搭建单点登录、统一授权系统。sso单点登录统一授权服务器、资源服务器Resource、应用。

资源服务器API通过浏览器或者是swagger测试调用传递中文参数皆能正常访问,但是在应用的后台通过网络编程调用出现如下错误:java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986。

出现错误原因:是因为tomcat高版本在 7.0.73, 8.0.39, 8.5.7 版本后,在http解析时做了严格限制。RFC3986文档规定,请求的Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符。

Http11Processor   : Error parsing HTTP request header
 Note: further occurrences of HTTP request parsing errors will be logged at DEBU
G level.

java.lang.IllegalArgumentException: Invalid character found in the request targe
t. The valid characters are defined in RFC 7230 and RFC 3986
        at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11Inp
utBuffer.java:467) ~[tomcat-embed-core-9.0.19.jar!/:9.0.19]
tomcat配置文件中:
IS_NOT_REQUEST_TARGET[]中定义了一堆not request target
if(IS_CONTROL[i] || i > 127 || i == 32 || i == 34 || i == 35 || i == 60 || i == 62 || i == 92 || i == 94 || i == 96 || i == 123 || i == 124 || i == 125) {
                IS_NOT_REQUEST_TARGET[i] = true;
            }

解决办法:网络上多数是修改tomcat的上述配置文件或者是降低版本或者是换成jetty。但是因为是springboot内嵌tomcat修改tomcat配置文件不可行,在yml文件中修改依然不起作用,使用如下配置依然不行,因为不知道如何将中文的加进去:

package com.hy.config;

import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TomcatConfig {

	@Bean
	public ConfigurableServletWebServerFactory webServerFactory() {
		TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
		factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {

			@Override
			public void customize(Connector connector) {
				connector.setProperty("relaxedQueryChars", "|{}[]");
			}
		});
		return factory;
	}

	/*
	 * @Bean public ServletWebServerFactory webServerFactory() {
	 * TomcatServletWebServerFactory fa = new TomcatServletWebServerFactory();
	 * fa.addConnectorCustomizers((TomcatConnectorCustomizer) connector ->
	 * connector.setProperty("relaxedQueryChars", "[]{}")); return fa; }
	 */

}

 突然的解决方案:

此前不断修改网络请求字符集编码格式,要么出错要么乱码解析不对,依然不能解决问题。偶然将浏览器中带中文的链接复制粘贴到文本文件中发现中文是经过编码后的。http://localhost:9091/testReq?userName=%E5%90%91%E4%B8%87%E6%9E%97&userNo=1150059。从此确认是中文发送请求前编码问题。

因此在网络编程中尝试进行编码后再发送请求,但是依然是乱码。通过修改网络请求时读取输入流的字符编码格式后解决问题。

  1. 资源服务器API:
@RequestMapping(value = "/testParam",method = RequestMethod.GET)
	public String testCH(@RequestParam("userName") String userName,@RequestParam("userNo") String userNo) {
		JSONObject jsonObject = new JSONObject();
		jsonObject.put("userName", userName);
		jsonObject.put("userNo", userNo);
		System.out.println("pathvariable:"+jsonObject.toString());
		return jsonObject.toString();		
	}

2.应用测试代码:

@Autowired
	RestTemplate restTemplate;

	@Test
	public void contextLoads() throws UnsupportedEncodingException {
		String nameString="测试";
		//String param = "userNo=1150059&userName="+nameString;
		String param = "userNo=1150059&userName="+URLEncoder.encode(nameString,"utf-8");
		//String param = "userNo=1150059&userName="+URLEncoder.encode("cs","utf-8");
		String targetURL ="http://localhost:9091/testParam";
		
		String encode =URLEncoder.encode(nameString,"utf-8");
		//encode =URLEncoder.encode(nameString);		
		String jSONString = HttpUtils.sendGet(targetURL, param);
		JSONObject jsonObject = WXHttpUtil.HttpRequest4Blocking(targetURL+"?"+param, QyWXApi.GET); //HttpUtils.sendGet("http://localhost:9091/isExist", param);
		System.out.println("JSONSTR_httputil_request:"+jSONString);
		System.out.println("jsonObject:"+jsonObject);
		
	}

3.网络请求封装工具代码

第一种方式:

/**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url   发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            log.info("sendGet - {}" , urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept" , "*/*" );
            connection.setRequestProperty("Charsert", "UTF-8"); //设置请求编码
            connection.setRequestProperty("connection" , "Keep-Alive" );
            connection.setRequestProperty("user-agent" , "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)" );
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
            log.info("recv - {}" , result);
        } catch (ConnectException e) {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        } catch (SocketTimeoutException e) {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        } catch (IOException e) {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        } catch (Exception e) {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception ex) {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }

第二种样式:

/**
	 * 发起网络请求-阻塞式
	 * 
	 * @param requestUrl
	 *            请求地址
	 * @param requestMethod
	 *            请求方式(GET、POST)
	 * @return JSONObject
	 */
	public static JSONObject HttpRequest4Blocking(String requestUrl,
			String requestMethod) {
		return HttpRequest4Blocking(requestUrl, requestMethod, null);
	}

	/**
	 * 发起网络请求-阻塞式
	 * 
	 * @param requestUrl
	 *            请求地址
	 * @param requestMethod
	 *            请求方式(GET、POST)
	 * @param outputStr
	 *            提交的数据
	 * @return JSONObject
	 */
	public static JSONObject HttpRequest4Blocking(String requestUrl,
			String requestMethod, String outputStr) {
		

		try {

			// buffer 缓冲流
			StringBuffer buffer = new StringBuffer();
			// 建立连接
			URL url = new URL(requestUrl);
			// 获取 HttpURLConnection对象
			HttpURLConnection connection = (HttpURLConnection) url
					.openConnection();
			// 设置输出流
			connection.setDoOutput(true);
			// 设置输入流
			connection.setDoInput(true);
			// 是否使用缓存
			connection.setUseCaches(false);
			// 请求方式
			connection.setRequestMethod(requestMethod);

			// 流不为空
			if (outputStr != null) {
				// 获取流
				OutputStream out = connection.getOutputStream();
				// 写数据流 UTF-8 编码
				out.write(outputStr.getBytes("UTF-8"));
				// 关闭
				out.close();
			}

			// 流处理
			InputStream input = connection.getInputStream();
			// 读取流 UTF-8 编码
			InputStreamReader inputReader = new InputStreamReader(input,
					"UTF-8");
			// 缓冲流 buffer
			BufferedReader reader = new BufferedReader(inputReader);

			// 定义 String
			String line;
			// 循环读取每一行,知道数据没有了,意思是读取完了
			while ((line = reader.readLine()) != null) {
				// 添加到StringBuffer字符流里面
				buffer.append(line);
			}

			// 关闭连接、释放资源
			reader.close();
			// 关闭
			inputReader.close();
			// 关闭
			input.close();
			// 释放、待gc
			input = null;
			// 关闭连接
			connection.disconnect();

			return JSONObject.fromObject(buffer.toString());

		} catch (Exception e) {
			System.out.println(e.getCause().toString());
		}

		return null;
	}