最近项目中的网络框架由AsyncHttp切换成了OKHttp,改动比较大,经过测试上线后,也确实担心会出问题,查看Bugly Crash记录,真是怕什么来什么……
出现了异常:
java.lang.IllegalArgumentException
Unexpected char 0x3000 at 35 in User-Agent value: Mozilla/5.0 (Linux; Android 5.0.2; Build/DBXCNOP5501001303S) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/37.0.0.0 Mobile Safari/537.36
java.lang.IllegalArgumentException
Unexpected char 0x950b at 35 in User-Agent value: Mozilla/5.0 (Linux; Android 4.4.4; 锋尚MAX Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/33.0.0.0 Mobile Safari/537.36
异常最终是抛在了Headers中的checkNameAndValue,OKHttp 设置请求头时,是不支持换行和中文字符的,在添加头时会做Header的Key和Value的检查,如果发现非法字符则抛出上述异常。
private void checkNameAndValue(String name, String value) { if (name == null) throw new IllegalArgumentException("name == null"); if (name.isEmpty()) throw new IllegalArgumentException("name is empty"); for (int i = 0, length = name.length(); i < length; i++) { char c = name.charAt(i); if (c <= '\u001f' || c >= '\u007f') { throw new IllegalArgumentException(String.format( "Unexpected char %#04x at %d in header name: %s", (int) c, i, name)); } } if (value == null) throw new IllegalArgumentException("value == null"); for (int i = 0, length = value.length(); i < length; i++) { char c = value.charAt(i); if (c <= '\u001f' || c >= '\u007f') { throw new IllegalArgumentException(String.format( "Unexpected char %#04x at %d in %s value: %s", (int) c, i, name, value)); } } }
因为服务端要统计一些信息,所以约定每个请求都需要上传UA。添加UA是通过实现NetworkInterceptor 网络拦截器实现每个请求都带有UA的,国产手机厂商会把手机型号加到UA里,也许是为了统计吧,之前的AsyncHttp没有这个问题,测试设备中也并未有ua带中文的测试机,这个问题被忽略掉,吐血…… 第一个异常是因为用了全角的空格,全角的空格Unicode码是\u3000。
解决方案:
因为UA服务端需要做统计,所以不能直接使用Base64编码,服务端没有解码逻辑需要对获取到的UA做合法性校验,遇到不符合条件的字符就过滤掉,过滤掉这些字符对统计应该不会有什么影响。
private static String getValidUA(String userAgent){ StringBuffer sb = new StringBuffer(); for (int i = 0, length = userAgent.length(); i < length; i++) { char c = userAgent.charAt(i); if (!(c <= '\u001f' || c >= '\u007f')) { sb.append(c); } } return sb.toString(); }在网上看到另外两种方案,也是可行的。
法1:检测为不合法字符,就转为unicode 编码,OkHttp 中的checkNameAndValue去遍历每个字符就不会为非法了
private static String getValidUA(String userAgent){ StringBuffer sb = new StringBuffer(); for (int i = 0, length = userAgent.length(); i < length; i++) { char c = userAgent.charAt(i); if (c <= '\u001f' || c >= '\u007f') { sb.append(String.format("\\u%04x", (int) c)); } else { sb.append(c); } } return sb.toString(); }
法2:如果发现非法字符,采用UrlEncode对其进行编码
private String getValidUA(String userAgent){ if(TextUtils.isEmpty(userAgent)){ return "android"; } String validUA = "android"; String uaWithoutLine = userAgent.replace("\n", ""); for (int i = 0, length = uaWithoutLine.length(); i < length; i++){ char c = userAgent.charAt(i); if (c <= '\u001f' || c >= '\u007f') { try { validUA = URLEncoder.encode(uaWithoutLine, "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return validUA; } } return uaWithoutLine; }