【JavaWeb】HttpClient
需要的依赖:
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency>
参考视频:
https://www.bilibili.com/video/BV1W54y1s7BZ
1、原生JDK实现的网络请求
这个在狂神的爬虫上面有看到过原生的方式
当时还不明白这个技术其实就是后台的Ajax
@Test public void quickStart() throws IOException { InputStream inputStream = null; InputStreamReader inputStreamReader = null; BufferedReader bufferedReader = null; try { // 原生JDK API 发送请求 String urlString = "https://www.baidu.com/"; URL url = new URL(urlString); URLConnection urlConnection = url.openConnection(); HttpURLConnection httpURLConnection = (HttpURLConnection) urlConnection; httpURLConnection.setRequestMethod("GET"); httpURLConnection.setRequestProperty("aaa", "123"); inputStream = httpURLConnection.getInputStream(); inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8); bufferedReader = new BufferedReader(inputStreamReader); String line = null; while (null != ( line = bufferedReader.readLine())) { System.out.println(line); } } catch (Exception e) { e.printStackTrace(); } finally { bufferedReader.close(); inputStreamReader.close(); inputStream.close(); } }
2、使用ApacheHttpClient发送请求:
@Test public void useHttpClient() throws IOException { // 使用 HttpClient Get 无参请求实现 CloseableHttpClient closeableHttpClient = null; CloseableHttpResponse closeableHttpResponse = null; HttpEntity httpEntity = null; try { // 创建一个可关闭的Http客户端对象 closeableHttpClient = HttpClients.createDefault(); // 要请求的地址 String urlString = "https://www.baidu.com/"; // GET参数需要进行URL编码处理 String param1 = "orderId=21343000123324"; String param2 = "orderRemark=大三大四1王企鹅1 哇多久啊是巴西 &%……¥%"; param1 = URLEncoder.encode(param1, StandardCharsets.UTF_8.name()); param2 = URLEncoder.encode(param2, StandardCharsets.UTF_8.name()); // 根据地址创建一个Http请求对象 这里使用的是GET请求对象 // HttpGet httpGet = new HttpGet(urlString); HttpGet httpGet = new HttpGet(urlString + "?" + param1 + "&" + param2); // 浏览器伪装信息 httpGet.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38"); // 防盗链设置 https://www.jianshu.com/p/0a1338db6cab httpGet.addHeader("Referer", "https://ntp.msn.cn/"); // 客户端对象带着请求对象 执行请求发送, 返回响应对象 closeableHttpResponse = closeableHttpClient.execute(httpGet); // 可以从响应对象获取对应的响应信息 StatusLine statusLine = closeableHttpResponse.getStatusLine(); System.out.println(statusLine); // HTTP/1.1 200 OK // 响应不成功状态直接结束后续逻辑 if (HttpStatus.SC_OK != statusLine.getStatusCode()) return; ProtocolVersion protocolVersion = statusLine.getProtocolVersion(); // HTTP/1.1 int major = protocolVersion.getMajor(); // 1 主版本协议号 int minor = protocolVersion.getMinor(); // 1 附属小版本协议号 String protocol = protocolVersion.getProtocol(); // HTTP int statusCode = statusLine.getStatusCode(); // 200 String reasonPhrase = statusLine.getReasonPhrase(); // OK Header[] allHeaders = closeableHttpResponse.getAllHeaders(); for (Header header : allHeaders) { System.out.println("Response Header -> " + header.getName() + " : " + header.getValue()); } // 从响应对象中获取响应实体对象 httpEntity = closeableHttpResponse.getEntity(); Header contentType = httpEntity.getContentType(); String contentTypeName = contentType.getName(); // Content-Type String contentTypeValue = contentType.getValue(); // Content-Type: text/html;charset=utf-8 // 这个响应头不常见 // Header contentEncoding = httpEntity.getContentEncoding(); // null // String contentEncodingName = contentEncoding.getName(); // String contentEncodingValue = contentEncoding.getValue(); // 使用实体工具类转换成字符结果 String httpEntityResult = EntityUtils.toString(httpEntity, StandardCharsets.UTF_8); // System.out.println(httpEntityResult); } catch (Exception exception) { exception.printStackTrace(); } finally { // 最后调用此方法确保资源释放 EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); } }
3、下载图片资源:
@Test public void downloadWebPicture() throws Exception { // 请求发送 CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); String resource = "https://img.zcool.cn/community/01088b5a052431a801204a0e253198.jpg@1280w_1l_2o_100sh.jpg"; HttpGet httpGet = new HttpGet(resource); CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet); HttpEntity httpEntity = closeableHttpResponse.getEntity(); // 类型解析 Header contentType = httpEntity.getContentType(); String contentTypeValue = contentType.getValue(); // image/jpeg String fileTypeSuffix = contentTypeValue.split("/")[1]; // jpeg // 文件下载 byte[] bytes = EntityUtils.toByteArray(httpEntity); String localPath = "C:\\Users\\Administrator\\Desktop\\test." + fileTypeSuffix; OutputStream outputStream = new FileOutputStream(localPath); outputStream.write(bytes); // 资源释放 outputStream.close(); EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
4、配置代理主机:
@Test public void proxySettings() throws Exception { CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); String target = "https://www.baidu.com/"; HttpGet httpGet = new HttpGet(target); // 代理主机的信息 http://www.66ip.cn/ String ip = "176.121.1.81"; int port = 8181; HttpHost httpHost = new HttpHost(ip, port); // 创建请求配置对象 RequestConfig requestConfig = RequestConfig .custom() .setProxy(httpHost) // 设置代理主机的信息 .build(); // 设置请求配置 httpGet.setConfig(requestConfig); CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet); HttpEntity httpEntity = closeableHttpResponse.getEntity(); Header[] allHeaders = closeableHttpResponse.getAllHeaders(); for (Header header : allHeaders) { System.out.println("Response Header -> " + header.getName() + " : " + header.getValue()); } String httpEntityResult = EntityUtils.toString(httpEntity, StandardCharsets.UTF_8); System.out.println(httpEntityResult); // 资源释放 EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
5、设置超时相关的配置:
@Test public void proxySettings() throws Exception { // 请求发送 CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); String target = "https://www.baidu.com/"; HttpGet httpGet = new HttpGet(target); // 代理主机的信息 http://www.66ip.cn/ String ip = "176.121.1.81"; int port = 8181; HttpHost httpHost = new HttpHost(ip, port); // 创建请求配置对象 RequestConfig requestConfig = RequestConfig .custom() .setProxy(httpHost) .setConnectTimeout(5000) // 设置连接超时的上限 TCP3次握手的时限 .setSocketTimeout(3000) // 设置读取超时上限 从请求的网址中获取响应数据的间隔时限(因为并不是一次请求就完成了加载) .setConnectionRequestTimeout(3000) // 从HttpClient连接池中获取connection对象的时限 .build(); // 设置请求配置 httpGet.setConfig(requestConfig); CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpGet); HttpEntity httpEntity = closeableHttpResponse.getEntity(); Header[] allHeaders = closeableHttpResponse.getAllHeaders(); for (Header header : allHeaders) { System.out.println("Response Header -> " + header.getName() + " : " + header.getValue()); } String httpEntityResult = EntityUtils.toString(httpEntity, StandardCharsets.UTF_8); System.out.println(httpEntityResult); // 资源释放 EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
6、MIME-TYPE 邮件扩展类型 与POST请求
mime-type 就是具体文件类型的前面的所属规范类型
在Tomcat里面配置的web.xml信息就可以看到所有的规范类型了
E:\apache-tomcat-8.5.70\conf\web.xml
片段:
<mime-mapping> <extension>zirz</extension> <mime-type>application/vnd.zul</mime-type> </mime-mapping> <mime-mapping> <extension>zmm</extension> <mime-type>application/vnd.handheld-entertainment+xml</mime-type> </mime-mapping>
常见Content-type:
# 一般html表单提交 发送的类型 application/x-www-form-urlencoded # html上传文件规范的类型 multipart/form-data # 目前主流规范的类型 application/json
POST表单类型提交案例:
1、客户端请求代码
@Test public void postWithFormType() throws Exception { CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); String target = "http://localhost:8080/mvc-framework/test/testMethod2"; // 首先创建POST请求对象 HttpPost httpPost = new HttpPost(target); // 设置表单需要提交的参数 List<NameValuePair> nvpList = new ArrayList<>(); nvpList.add(new BasicNameValuePair("username", "张三")); nvpList.add(new BasicNameValuePair("password", "w123e21")); // 表单类型实体对象 装填参数信息 application/x-www-form-urlencoded UrlEncodedFormEntity urlEncodedFormEntity = new UrlEncodedFormEntity(nvpList, Consts.UTF_8); // 设置表单类型实体对象 httpPost.setEntity(urlEncodedFormEntity); // 客户端执行请求发送 CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost); HttpEntity httpEntity = closeableHttpResponse.getEntity(); System.out.println(EntityUtils.toString(httpEntity, StandardCharsets.UTF_8)); EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
2、服务器处理代码:
/** * * POST请求测试 * http://localhost:8080/mvc-framework/test/testMethod2 * @param request * @param response */ // @RequestMethod(MethodType.POST) @RequestMapping(value = "/testMethod2", methodType = MethodType.POST) public void testMethod2(HttpServletRequest request, HttpServletResponse response) { System.out.println("testMethod2"); ServletUtil.printAllParamByRequestMap(request); Map<String, Object> requestAttribute = (Map<String, Object>) request.getAttribute(ServletConstants.POST_PARAM_KEY); for (String s : requestAttribute.keySet()) { System.out.println("postParam " + s + ": " + requestAttribute.get(s)); } }
服务这里没有对请求做出响应,就是打印看看没有收到参数信息:
doPost detected
doGet detected
testMethod2
username: [张三]
password: [w123e21]
postParam password: w123e21
postParam username: 张三
做了两次打印的原因是第一个打印是直接调用ServletAPI实现:
基本的POST表单请求Servlet有做识别处理
public static void printAllParamByRequestMap(HttpServletRequest request) { Map<String, String[]> parameterMap = request.getParameterMap(); for (String s : parameterMap.keySet()) { System.out.println(s + ": " + Arrays.toString(parameterMap.get(s))); } }
第二次打印是从输入流中获取识别的
/** * 从POST请求中获取参数 * @param request * @return * @throws Exception */ public static Map<String, Object> getPostParam(HttpServletRequest request) throws Exception { // 返回参数 Map<String, Object> params = new HashMap<>(); // 获取内容格式 String contentType = request.getContentType(); if (null == contentType || "".equals(contentType)) throw new ServletException("没有设置请求头项Content-Type!!!"); else contentType = contentType.split(";")[0]; // form表单格式 表单形式可以从 ParameterMap中获取 if (ServletConstants.CONTENT_TYPE_VALUE_URL_ENCODED2.equalsIgnoreCase(contentType)) { // 获取参数 Map<String, String[]> parameterMap = request.getParameterMap(); if (parameterMap != null) { for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) { params.put(entry.getKey(), entry.getValue()[0]); } } } // json格式 json格式需要从request的输入流中解析获取 if (ServletConstants.CONTENT_TYPE_VALUE_JSON2.equalsIgnoreCase(contentType)) { // 使用 commons-io中 IOUtils 类快速获取输入流内容 String paramJson = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8); Map parseObject = JSON.parseObject(paramJson, Map.class); params.putAll(parseObject); } return params ; }
也可以不使用 UrlEncodedFormEntity ,使用请求头设置即可
这个意思在视频里说错了,应该是这个Entity已经在Header这么处理了
// 配置Http请求头 httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
因为表单的参数还是需要放到Entity里面发送过去的
这里特意看了下Entity的实现结构:
搜寻Entity相关的时候发现这个博客写的也很好,涉及到Cookie相关的操作
https://www.cnblogs.com/licl11092/p/9075677.html
POST + JSON类型案例:
SpringMVC接受Post JSON数据时要带上 @RequestBody给方法
普通Get参数则是@RequestParam
类注解为@RestController就可以不注解@RequestBody方法
在这里我使用的是封装的一套MVC,Servlet对JSON参数是不支持的,只能从输入流自行获取
这里JSON参数采用的是StringEntity实现存储,看了源码发现默认是text/plain类型,也允许构造器自行设置类型
@Test public void postWithFormJson() throws Exception { CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); java.lang.String target = "http://localhost:8080/mvc-framework/test/testMethod2"; // 首先创建POST请求对象 HttpPost httpPost = new HttpPost(target); // 设置需要提交的JSON参数 这里做简单案例,就不去下载Fastjson来转换了,自己手写一个 String jsonParam = "{ \"username\": \"张三\", \"password\": \"w123e21\" }"; // 表单类型实体对象 装填参数信息 application/x-www-form-urlencoded StringEntity jsonEntity = new StringEntity(jsonParam , Consts.UTF_8); // 配置Http请求头 // httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); jsonEntity.setContentType(new BasicHeader("Content-Type", "application/json; charset=UTF-8")); // StringEntity 设置编码 jsonEntity.setContentEncoding(Consts.UTF_8.name()); // 设置JSON实体对象 httpPost.setEntity(jsonEntity); // 客户端执行请求发送 CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost); HttpEntity httpEntity = closeableHttpResponse.getEntity(); System.out.println(EntityUtils.toString(httpEntity, StandardCharsets.UTF_8)); EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
POST + 文件类型案例:
首先测试Html表单上传文件处理:
上传操作的页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>静态资源访问 3333</h3> <form action="http://localhost:8080/mvc-framework/test/fileUploadTest" enctype="multipart/form-data" method="post"> <p><label >上传文件</label> <input type="file" name="upload" ></p> <p><label >用户名</label> <input type="text" name="username"></p> <input type="submit" value="提交"> </form> </body> </html>
Servlet后台:
/** * POST数据处理 * http://localhost:8080/mvc-framework/test/fileUploadTest * @return */ @RequestMapping(value = "/fileUploadTest", methodType = MethodType.POST) public Map<String, Object> fileUploadTest(HttpServletRequest request) throws ServletException, IOException { String contentType = request.getContentType(); // request.setCharacterEncoding("UTF-8"); 注意中文乱码 if (null == contentType || "".equals(contentType)) throw new ServletException("ContentType类型未声明!"); System.out.println(contentType); if (!contentType.contains("multipart/form-data")) throw new ServletException("ContentType类型不符合文件上传的要求!"); //获取上传的文件集合(应该是所有表单数据封装成的part对象集合) Collection<Part> parts = request.getParts(); if(null == parts) throw new ServletException("没有需要上传的文件!"); for (Part part : parts) { //Servlet3没有提供直接获取文件名的方法,需要从请求头中解析出来 //获取请求头,请求头的格式:form-data; name="file"; filename="snmp4j--api.zip" String header = part.getHeader("content-disposition"); // 注意这个part只有文件才会有 filename属性 if (!header.contains("filename")) continue; /** * 每个浏览器的输出情况 * Edge -> form-data; name="upload"; filename="QQ截图20210817220501.png" * 360极速 -> form-data; name="upload"; filename="C:\Users\Administrator\Pictures\QQ截图20210820005513.png" * 火狐 -> form-data; name="upload"; filename="QQ截图20210817220501.png" * 谷歌 -> form-data; name="upload"; filename="QQ截图20210817220501.png" * IE -> form-data; name="upload"; filename="C:\Users\Administrator\Pictures\QQ截图20210817220501.png" */ // 取得文件名称与后缀信息 String[] nameWithSuffix = ServletUtil.getFileNameWithSuffix(header); // 获取本地位置: String savePath = request.getServletContext().getRealPath("/WEB-INF/upload"); System.out.println(savePath); // 如果这个路径没有就创建一个出来 File storagePosition = new File(savePath); if (!storagePosition.exists()) storagePosition.mkdirs(); // mkdirs 允许创建多层级目录 // 输出写入到本地 part.write(savePath + File.separator + nameWithSuffix[0]); } Map<String, Object> res = new HashMap<>(); res.put("status", 200); res.put("msg", "OK"); res.put("data", "文件上传成功!!!"); return res; }
注意,这里我发现原来是没有使用Servlet3.0提供的内容来解决文件上传下载的
用的ApacheCommonsIO + FileUpload
后来才知道3.0已经提供文件上传的一些功能:
@MultipartConfig注解,注解在你需要做文件上传的Servlet类上面
package javax.servlet.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface MultipartConfig { String location() default ""; long maxFileSize() default -1L; long maxRequestSize() default -1L; int fileSizeThreshold() default 0; }
当你想从Servlet获取上传的文件,但是没有声明此注解
Tomcat会报错:
由于没有提供multi-part配置,无法处理parts
类同SpringMVC一样
由于我是封装的MVC框架,所有Servlet处理的后台在DispatchServlet中调用
所以对他配置注解:
@MultipartConfig public class DispatchServlet extends HttpServlet { // private Class<?> thisClass = this.getClass(); private static Mapping currentMapping; private static Map<String, Mapping> allRequestMapping; public static final String REDIRECT = "redirect:"; public static String dispatchPath = "/WEB-INF/jsp/"; public static String fileSuffix = ".jsp";
下面是后台逻辑:
/** * POST数据处理 * http://localhost:8080/mvc-framework/test/fileUploadTest * @return */ @RequestMapping(value = "/fileUploadTest", methodType = MethodType.POST) public Map<String, Object> fileUploadTest(HttpServletRequest request) throws ServletException, IOException { String contentType = request.getContentType(); // request.setCharacterEncoding("UTF-8"); 注意中文乱码 if (null == contentType || "".equals(contentType)) throw new ServletException("ContentType类型未声明!"); System.out.println(contentType); if (!contentType.contains("multipart/form-data")) throw new ServletException("ContentType类型不符合文件上传的要求!"); //获取上传的文件集合(应该是所有表单数据封装成的part对象集合) Collection<Part> parts = request.getParts(); if(null == parts) throw new ServletException("没有需要上传的文件!"); for (Part part : parts) { //Servlet3没有提供直接获取文件名的方法,需要从请求头中解析出来 //获取请求头,请求头的格式:form-data; name="file"; filename="snmp4j--api.zip" String header = part.getHeader("content-disposition"); // 注意这个part只有文件才会有 filename属性 if (!header.contains("filename")) continue; /** * 每个浏览器的输出情况 * Edge -> form-data; name="upload"; filename="QQ截图20210817220501.png" * 360极速 -> form-data; name="upload"; filename="C:\Users\Administrator\Pictures\QQ截图20210820005513.png" * 火狐 -> form-data; name="upload"; filename="QQ截图20210817220501.png" * 谷歌 -> form-data; name="upload"; filename="QQ截图20210817220501.png" * IE -> form-data; name="upload"; filename="C:\Users\Administrator\Pictures\QQ截图20210817220501.png" */ // 取得文件名称与后缀信息 String[] nameWithSuffix = ServletUtil.getFileNameWithSuffix(header); // 获取本地位置: String savePath = request.getServletContext().getRealPath("/WEB-INF/upload"); System.out.println(savePath); // 如果这个路径没有就创建一个出来 File storagePosition = new File(savePath); if (!storagePosition.exists()) storagePosition.mkdirs(); // mkdirs 允许创建多层级目录 // 输出写入到本地 part.write(savePath + File.separator + nameWithSuffix[0]); } Map<String, Object> res = new HashMap<>(); res.put("status", 200); res.put("msg", "OK"); res.put("data", "文件上传成功!!!"); return res; }
这个功能比之前的Apache封装要简单多了
直接可以输出写入到本地
追加上获取文件名的办法:
public static String[] getFileNameWithSuffix(String partHeader) throws ServletException { String[] fileNameArr = new String[2]; if(!partHeader.contains("filename")) throw new ServletException("找不到文件名称"); String filename = partHeader.split("filename")[1]; filename = filename.replace("=", ""); filename = filename.replace("\"", ""); if (filename.contains("\\")) { // 例如IE的情况,带有绝对路径信息 filename = filename.substring(filename.lastIndexOf("\\")); } fileNameArr[0] = filename; fileNameArr[1] = filename.substring(filename.lastIndexOf(".")); return fileNameArr; }
视频里面用的IE浏览器,可以抓到这个Content-Disposition 信息
但是我用Edge是看不到的
同样用IE看也没有。。。
使用HttpClient实现文件上传:
需要新增一个MIME依赖:
<!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5.13</version> </dependency>
客户端代码:
@Test public void postWithFormMultiPart() throws Exception { CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); java.lang.String target = "http://localhost:8080/mvc-framework/test/fileUploadTest"; // 首先创建POST请求对象 HttpPost httpPost = new HttpPost(target); // 创建文件上传实体对象 MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create(); HttpEntity httpEntity = multipartEntityBuilder // 设置ContentType 类型 .setContentType(ContentType.MULTIPART_FORM_DATA) // 设置编码,编码重置 .setCharset(Consts.UTF_8) // 设置模式(浏览器模式) .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) // 添加附件 .addBinaryBody("project-repo.txt", new File("C:\\Users\\Administrator\\Desktop\\project-repo.txt")) // 添加part附件 .addPart("partFile", new FileBody(new File("C:\\Users\\Administrator\\Pictures\\QQplayerPic\\4[00_00_50][20210919-092125].png"))) // 添加文本附件 但是文本附件不会被上传到服务器里面 原因可能是不具备filename .addTextBody("txtBody1", "张三") .addTextBody("txtBody2", "1qwdasx") .build(); // 设置文件上传实体对象 httpPost.setEntity(httpEntity); // 客户端执行请求发送 CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost); httpEntity = closeableHttpResponse.getEntity(); System.out.println(EntityUtils.toString(httpEntity, StandardCharsets.UTF_8)); EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
关于addPart下面一个重载方法:
客户端响应:
{"msg":"OK","data":"文件上传成功!!!","status":200}
服务器只触发了两次:
说明下面的两个textBody是不识别的,因为我设置了方法
doPost detected doGet detected multipart/form-data; boundary=0Oz1eCUwv_bwYAEDfOi5K-s4DshztEqsLWsW5; charset=UTF-8 C:\Users\Administrator\IdeaProjects\MVC-Framework\out\artifacts\MVC_Framwork\WEB-INF\upload C:\Users\Administrator\IdeaProjects\MVC-Framework\out\artifacts\MVC_Framwork\WEB-INF\upload
我放开打印再看看结果:
可以看到文本体就是没有filename的存在
form-data; name="project-repo.txt"; filename="project-repo.txt"
C:\Users\Administrator\IdeaProjects\MVC-Framework\out\artifacts\MVC_Framwork\WEB-INF\upload
form-data; name="partFile"; filename="4[00_00_50][20210919-092125].png"
C:\Users\Administrator\IdeaProjects\MVC-Framework\out\artifacts\MVC_Framwork\WEB-INF\upload
form-data; name="txtBody1"
form-data; name="txtBody2"
也就是textBody相当于input标签,设置类型type="text"了
这里存在一个中文乱码的问题,并不是过滤器能够解决的了
txtBody1: [??]
txtBody2: [1qwdasx]
客户端发送就会乱码
因为建造器不支持对应的中文字符
// 添加文本附件 但是文本附件不会被上传到服务器里面 原因可能是不具备filename
.addTextBody("txtBody1", "张三")
.addTextBody("txtBody2", "1qwdasx")
需要这样编写:
// 添加文本附件 但是文本附件不会被上传到服务器里面 原因可能是不具备filename .addPart("txtBody1", new StringBody("张三", ContentType.create("text/plain", Consts.UTF_8))) .addTextBody("txtBody2", "1qwdasx")
再打印就正常了:
doPost detected doGet detected txtBody1: [张三] txtBody2: [1qwdasx] multipart/form-data; boundary=VBLteRGDXG2JWQyo3e3o7Ez8Wzzzb-u4fOURku0; charset=UTF-8 form-data; name="project-repo.txt"; filename="project-repo.txt" C:\Users\Administrator\IdeaProjects\MVC-Framework\out\artifacts\MVC_Framwork\WEB-INF\upload form-data; name="partFile"; filename="4[00_00_50][20210919-092125].png" C:\Users\Administrator\IdeaProjects\MVC-Framework\out\artifacts\MVC_Framwork\WEB-INF\upload form-data; name="txtBody1" form-data; name="txtBody2"
7、HTTPS安全认证越过:
这里借用JSON的那个请求测试
唯一的区别是请求地址追加了s,使用HTTPS认证
然后客户端请求就会报错
@Test public void sslCross() throws Exception { CloseableHttpClient closeableHttpClient = HttpClients.createDefault(); java.lang.String target = "https://localhost:8080/mvc-framework/test/testMethod2"; // 首先创建POST请求对象 HttpPost httpPost = new HttpPost(target); // 设置需要提交的JSON参数 这里做简单案例,就不去下载Fastjson来转换了,自己手写一个 String jsonParam = "{ \"username\": \"张三\", \"password\": \"w123e21\" }"; // 表单类型实体对象 装填参数信息 application/x-www-form-urlencoded StringEntity jsonEntity = new StringEntity(jsonParam , Consts.UTF_8); // 配置Http请求头 // httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); jsonEntity.setContentType(new BasicHeader("Content-Type", "application/json; charset=UTF-8")); // StringEntity 设置编码 jsonEntity.setContentEncoding(Consts.UTF_8.name()); // 设置JSON实体对象 httpPost.setEntity(jsonEntity); // 客户端执行请求发送 CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost); HttpEntity httpEntity = closeableHttpResponse.getEntity(); System.out.println(EntityUtils.toString(httpEntity, StandardCharsets.UTF_8)); EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
报错信息:
"C:\Program Files (x86)\Java\jdk1.8.0_291\bin\java.exe" -ea -Didea.test.cyclic.buffer.size=1048576 "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar=60373:C:\Program Files\JetBrains\IntelliJ IDEA 2021.2.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA 2021.2.1\lib\idea_rt.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2021.2.1\plugins\junit\lib\junit5-rt.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2021.2.1\plugins\junit\lib\junit-rt.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\access-bridge-32.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\cldrdata.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\jaccess.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\jfxrt.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\nashorn.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\sunec.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\sunpkcs11.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\ext\zipfs.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\jfr.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\jfxswt.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.8.0_291\jre\lib\rt.jar;C:\Users\Administrator\IdeaProjects\Anything\Framework\target\test-classes;C:\Users\Administrator\IdeaProjects\Anything\Framework\target\classes;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpclient\4.5.13\httpclient-4.5.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpcore\4.4.12\httpcore-4.4.12.jar;C:\Users\Administrator\.m2\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;C:\Users\Administrator\.m2\repository\commons-codec\commons-codec\1.13\commons-codec-1.13.jar;C:\Users\Administrator\.m2\repository\org\apache\httpcomponents\httpmime\4.5.13\httpmime-4.5.13.jar;C:\Users\Administrator\.m2\repository\javax\servlet\javax.servlet-api\4.0.1\javax.servlet-api-4.0.1.jar;C:\Users\Administrator\.m2\repository\javax\servlet\jstl\1.2\jstl-1.2.jar;C:\Users\Administrator\.m2\repository\javax\servlet\jsp\javax.servlet.jsp-api\2.3.3\javax.servlet.jsp-api-2.3.3.jar;C:\Users\Administrator\.m2\repository\org\reflections\reflections\0.9.11\reflections-0.9.11.jar;C:\Users\Administrator\.m2\repository\com\google\guava\guava\20.0\guava-20.0.jar;C:\Users\Administrator\.m2\repository\junit\junit\4.12\junit-4.12.jar;C:\Users\Administrator\.m2\repository\org\hamcrest\hamcrest-core\2.1\hamcrest-core-2.1.jar;C:\Users\Administrator\.m2\repository\org\hamcrest\hamcrest\2.1\hamcrest-2.1.jar;C:\Users\Administrator\.m2\repository\commons-io\commons-io\2.11.0\commons-io-2.11.0.jar;C:\Users\Administrator\.m2\repository\commons-beanutils\commons-beanutils\1.7.0\commons-beanutils-1.7.0.jar;C:\Users\Administrator\.m2\repository\org\javassist\javassist\3.26.0-GA\javassist-3.26.0-GA.jar;C:\Users\Administrator\.m2\repository\com\alibaba\fastjson\1.2.78\fastjson-1.2.78.jar" com.intellij.rt.junit.JUnitStarter -ideVersion5 -junit4 HttpClientTest,sslCross javax.net.ssl.SSLException: Unsupported or unrecognized SSL message at sun.security.ssl.SSLSocketInputRecord.handleUnknownRecord(SSLSocketInputRecord.java:448) at sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:174) at sun.security.ssl.SSLTransport.decode(SSLTransport.java:110) at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1290) at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1199) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:401) at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:373) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436) at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384) at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142) at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89) at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108) at HttpClientTest.sslCross(HttpClientTest.java:348) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54) Process finished with exit code -1
同样服务器也报错:
05-Oct-2021 14:40:01.846 信息 [http-nio-8080-exec-6] org.apache.coyote.http11.Http11Processor.service 解析 HTTP 请求 header 错误
注意:HTTP请求解析错误的进一步发生将记录在DEBUG级别。
java.lang.IllegalArgumentException: 在方法名称中发现无效的字符串, HTTP 方法名必须是有效的符号.
at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:434)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:511)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:831)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1650)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
这里没证书,一笔带过了,直接上代码:
主要是对客户端设置相关的SSL机制,其余的都是一样使用
@Test public void sslCross() throws Exception { SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { return true; } }); SSLContext sslContext = sslContextBuilder.build(); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, new String[]{ "SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE ); Registry<ConnectionSocketFactory> registry = RegistryBuilder .<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.INSTANCE) .register("https", sslConnectionSocketFactory) .build(); PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry); // 客户端对象需要定制化 HttpClientBuilder httpClientBuilder = HttpClients.custom().setConnectionManager(poolingHttpClientConnectionManager); CloseableHttpClient closeableHttpClient = httpClientBuilder.build(); java.lang.String target = "https://localhost:8080/mvc-framework/test/testMethod2"; // 首先创建POST请求对象 HttpPost httpPost = new HttpPost(target); // 设置需要提交的JSON参数 这里做简单案例,就不去下载Fastjson来转换了,自己手写一个 String jsonParam = "{ \"username\": \"张三\", \"password\": \"w123e21\" }"; // 表单类型实体对象 装填参数信息 application/x-www-form-urlencoded StringEntity jsonEntity = new StringEntity(jsonParam , Consts.UTF_8); // 配置Http请求头 // httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); jsonEntity.setContentType(new BasicHeader("Content-Type", "application/json; charset=UTF-8")); // StringEntity 设置编码 jsonEntity.setContentEncoding(Consts.UTF_8.name()); // 设置JSON实体对象 httpPost.setEntity(jsonEntity); // 客户端执行请求发送 CloseableHttpResponse closeableHttpResponse = closeableHttpClient.execute(httpPost); HttpEntity httpEntity = closeableHttpResponse.getEntity(); System.out.println(EntityUtils.toString(httpEntity, StandardCharsets.UTF_8)); EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); }
8、连接池与工具封装
package cn.dzz.framework.util; import org.apache.http.*; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicHeader; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.TrustStrategy; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import javax.servlet.ServletException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; public class HttpClientUtil { private static final HttpClientBuilder httpClientBuilder = HttpClients.custom(); static { try { // 连接管理器 SSLContextBuilder sslContextBuilder = new SSLContextBuilder(); sslContextBuilder.loadTrustMaterial(null, new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { return true; } }); SSLContext sslContext = sslContextBuilder.build(); SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory( sslContext, new String[]{ "SSLv2Hello", "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE ); Registry<ConnectionSocketFactory> registry = RegistryBuilder .<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.INSTANCE) .register("https", sslConnectionSocketFactory) .build(); PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(registry); poolingHttpClientConnectionManager.setMaxTotal(50);// 连接池最大连接数量 poolingHttpClientConnectionManager.setDefaultMaxPerRoute(50); // 每个路由的默认连接 路由 = ip + port httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager); // 请求默认配置 RequestConfig requestConfig = RequestConfig .custom() .setConnectTimeout(6000) .setSocketTimeout(3000) .setConnectionRequestTimeout(6000) .build(); httpClientBuilder.setDefaultRequestConfig(requestConfig); // 默认请求头配置 List<Header> headerList = new ArrayList<>(); BasicHeader agentHeader = new BasicHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Edg/94.0.992.38"); headerList.add(agentHeader); httpClientBuilder.setDefaultHeaders(headerList); } catch (Exception e) { e.printStackTrace(); } } /** * 发送GET请求 * @param url * @param headers * @return */ public static String executeGetRequest(String url, Map<String, String> headers) { CloseableHttpClient closeableHttpClient = null; CloseableHttpResponse closeableHttpResponse = null; HttpEntity httpEntity = null; String resJson = null; try { closeableHttpClient = httpClientBuilder.build(); // 配置请求地址 HttpGet httpGet = new HttpGet(url); // 配置请求头信息 for (String key : headers.keySet()) httpGet.addHeader(key, headers.get(key)); // 请求发送 closeableHttpResponse = closeableHttpClient.execute(httpGet); // 状态判定 StatusLine statusLine = closeableHttpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); if ( ! isSuccessRequest(statusCode)) throw new ServletException(url + " 请求失败, " + statusLine); httpEntity = closeableHttpResponse.getEntity(); resJson = EntityUtils.toString(httpEntity); } catch (Exception exception) { exception.printStackTrace(); return null; } finally { try { EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); } catch (Exception exception) { exception.printStackTrace(); } } return resJson; } /** * 发送POST请求 * @param url * @param headers * @return */ public static String executeGetRequest(String url, String json, Map<String, String> headers) { CloseableHttpClient closeableHttpClient = null; CloseableHttpResponse closeableHttpResponse = null; HttpEntity httpEntity = null; String resJson = null; try { closeableHttpClient = httpClientBuilder.build(); // 配置请求地址 HttpPost httpPost = new HttpPost(url); // 配置请求头信息 for (String key : headers.keySet()) httpPost.addHeader(key, headers.get(key)); httpPost.addHeader("Content-Type", "application/json; charset=UTF-8"); // 请求体设置 StringEntity jsonEntity = new StringEntity(json, Consts.UTF_8); jsonEntity.setContentEncoding(Consts.UTF_8.name()); jsonEntity.setContentType("application/json; charset=UTF-8"); httpPost.setEntity(jsonEntity); // 请求发送 closeableHttpResponse = closeableHttpClient.execute(httpPost); // 状态判定 StatusLine statusLine = closeableHttpResponse.getStatusLine(); int statusCode = statusLine.getStatusCode(); if ( ! isSuccessRequest(statusCode)) throw new ServletException(url + " 请求失败, " + statusLine); httpEntity = closeableHttpResponse.getEntity(); resJson = EntityUtils.toString(httpEntity); } catch (Exception exception) { exception.printStackTrace(); return null; } finally { try { EntityUtils.consume(httpEntity); closeableHttpResponse.close(); closeableHttpClient.close(); } catch (Exception exception) { exception.printStackTrace(); } } return resJson; } /** * 这里判定条件宽泛,只要是2和3都算请求成功 * @param statusCode * @return */ public static boolean isSuccessRequest(int statusCode) { boolean flag = false; switch (statusCode) { // 2XX状态 case HttpStatus.SC_OK: case HttpStatus.SC_CREATED: case HttpStatus.SC_ACCEPTED: case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION: case HttpStatus.SC_NO_CONTENT: case HttpStatus.SC_RESET_CONTENT: case HttpStatus.SC_PARTIAL_CONTENT: case HttpStatus.SC_MULTI_STATUS: case HttpStatus.SC_MULTIPLE_CHOICES: // 3XX状态 case HttpStatus.SC_MOVED_PERMANENTLY: case HttpStatus.SC_MOVED_TEMPORARILY: case HttpStatus.SC_SEE_OTHER: case HttpStatus.SC_NOT_MODIFIED: case HttpStatus.SC_USE_PROXY: case HttpStatus.SC_TEMPORARY_REDIRECT: flag = true; break; default: } return flag; } }