一次HttpClient工具类引发的生产事故

对接新的三方支付平台,在测试环境验证完成后,结果投产时出现了调不通的情况。换了HttpClient工具类后,结果就可以调通了。最终定位到原因是HttpClient工具类的编码类型导致的。

编码类型由属性enctype决定。它可以有三个值

  • application/x-www-form-urlencoded: 表示使用URL编码的方式来编码表单。如果没有将enctype属性设置为任何值,那么这就是默认值。
  • multipart/form-data: 当用户想上传文件这种二进制等文件或者前面的那个方式不能满足时,使用这种类型的表单
  • text/plain: 文本形式,只发送数据而不进行任何编码时使用。

application/x-www-form-urlencoded使用 & 来分隔键值对,使用 = 来连接键值对。如:username=1111&password=123456
如果有空格的话,发送的数据是被编码(见百分比编码),数据如下:username=11%2011&password=123456

百分比编码 是一种拥有8位字符编码的编码机制,这些编码在URL的上下文中具有特定的含义。它有时被称为URL编码。编码由英文字母替换组成:“%” 后跟替换字符的ASCII的十六进制表示。

需要编码的特殊字符有: ‘:’,’/’,’?’,’#’,’[’,’]’,’@’,’!’,’$’,’&’,"’",’(’,’)’,’*’,’+’,’,’,’;’,’=’,以及,’%’ 本身. 其他的字符虽然可以进行编码但是不需要。

‘:’‘/’‘?’‘#’‘[’‘]’‘@’‘!’‘$’‘&’“’”‘(’‘)’‘*’‘+’‘,’‘;’‘=’‘%’’ ’
%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2A%2B%2C%3B%3D%25%20 或 +

根据上下文, 空白符 ’ ’ 将会转换为 ‘+’ (必须在HTTP的POST方法中使定义 application/x-www-form-urlencoded 传输方式), 或者将会转换为 ‘%20’ 的 URL。

前后工具类对比:

之前的工具类:

public static String postData(String url, String sendData, Map<String,String> headerMap, String chartset) throws Exception{
		String result = "";
		CloseableHttpResponse resp = null;
		try{
			if(StringUtils.isBlank(chartset)){
				chartset = "utf-8";
			}
			CloseableHttpClient client = getHttpClient(url);
			HttpPost httpPost = new HttpPost(url);
			config(httpPost);

			httpPost.addHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
			if(headerMap != null && !headerMap.isEmpty()){
				for(String name:headerMap.keySet()){
					httpPost.addHeader(name,headerMap.get(name));
				}
			}
			httpPost.addHeader("Accept-Charset", chartset);
			StringEntity entity = new StringEntity(sendData, chartset);
			entity.setContentEncoding(chartset);
			entity.setContentType("application/x-www-form-urlencoded");
			entity.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded"));
			httpPost.setEntity(entity);
			resp = client.execute(httpPost);
			if(resp.getStatusLine().getStatusCode() == 200) {
				HttpEntity he = resp.getEntity();
				if(he!=null){
					result = EntityUtils.toString(he, chartset);
				}
			}else {
				handlerHttpStatusCode(resp.getStatusLine().getStatusCode());
			}
		}catch(Exception e){
			logger.error("postData", e);
			throw e;
		}finally {
			try {
				if(resp!=null) {
					resp.close();
				}
			} catch (IOException e) {
				logger.error("post", e);
			}
		}
		return result;
	}

之后的工具类:

	public static String post(String requestUrl, String requestContent) throws IOException {
		org.apache.http.entity.StringEntity requestEntity = new org.apache.http.entity.StringEntity(requestContent);
		return post(requestUrl,requestEntity);
	}
		public static String post(String requestUrl, org.apache.http.entity.HttpEntity httpEntity) throws IOException {
		String result = null;
		HttpPost httpPost = new HttpPost(requestUrl);
		httpPost.setEntity(httpEntity);

		try {
			CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
			try {
				HttpEntity entity = httpResponse.getEntity();
				if (httpResponse.getStatusLine().getReasonPhrase().equals("OK") || httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
					result = EntityUtils.toString(entity, "UTF-8");
				}
				EntityUtils.consume(entity);
			} finally {
				if (null != httpResponse) {
					httpResponse.close();
				}
			}
		} finally {
			if (null != httpPost) {
				httpPost.releaseConnection();
			}
		}
		return result;
	}

StringEntity的默认编码类型为: text/plain

    public StringEntity(String string) throws UnsupportedEncodingException {
        this(string, ContentType.DEFAULT_TEXT);
    }
    static {
        APPLICATION_ATOM_XML = create("application/atom+xml", Consts.ISO_8859_1);
        APPLICATION_FORM_URLENCODED = create("application/x-www-form-urlencoded", Consts.ISO_8859_1);
        APPLICATION_JSON = create("application/json", Consts.UTF_8);
        APPLICATION_OCTET_STREAM = create("application/octet-stream", (Charset)null);
        APPLICATION_SVG_XML = create("application/svg+xml", Consts.ISO_8859_1);
        APPLICATION_XHTML_XML = create("application/xhtml+xml", Consts.ISO_8859_1);
        APPLICATION_XML = create("application/xml", Consts.ISO_8859_1);
        MULTIPART_FORM_DATA = create("multipart/form-data", Consts.ISO_8859_1);
        TEXT_HTML = create("text/html", Consts.ISO_8859_1);
        TEXT_PLAIN = create("text/plain", Consts.ISO_8859_1);
        TEXT_XML = create("text/xml", Consts.ISO_8859_1);
        WILDCARD = create("*/*", (Charset)null);
        DEFAULT_TEXT = TEXT_PLAIN;
        DEFAULT_BINARY = APPLICATION_OCTET_STREAM;
    }

结论:为了防止测试环境与生产环境存在差异导致同类事故发生,我们后续有了这样的规范:

对接新三方,测试环境验证完成后,须将配置信息改成生产信息,在生产环境进行完整的测试,整个流程均无问题后,方可投产

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页