001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.components.http.jboot; 017 018import com.jfinal.log.Log; 019import io.jboot.components.http.HttpMimeTypes; 020import io.jboot.components.http.JbootHttp; 021import io.jboot.components.http.JbootHttpRequest; 022import io.jboot.components.http.JbootHttpResponse; 023import io.jboot.exception.JbootException; 024import io.jboot.utils.ArrayUtil; 025import io.jboot.utils.QuietlyUtil; 026import io.jboot.utils.StrUtil; 027 028import javax.net.ssl.*; 029import java.io.*; 030import java.net.HttpCookie; 031import java.net.HttpURLConnection; 032import java.net.ProtocolException; 033import java.net.URL; 034import java.security.KeyStore; 035import java.security.SecureRandom; 036import java.security.cert.X509Certificate; 037import java.util.List; 038import java.util.Map; 039import java.util.zip.GZIPInputStream; 040 041 042public class JbootHttpImpl implements JbootHttp { 043 044 private static final Log LOG = Log.getLog(JbootHttpImpl.class); 045 046 047 @Override 048 public JbootHttpResponse handle(JbootHttpRequest request) { 049 JbootHttpResponse response = new JbootHttpResponse(request); 050 doProcess(request, response); 051 return response; 052 } 053 054 055 private void doProcess(JbootHttpRequest request, JbootHttpResponse response) { 056 HttpURLConnection connection = null; 057 InputStream inStream = null; 058 try { 059 060 //获取 http 链接 061 connection = getConnection(request); 062 063 //配置 http 链接 064 configConnection(connection, request); 065 066 067 //post 或者 put 请求 068 if (request.isPostRequest() || request.isPutRequest()) { 069 070 connection.setDoOutput(true); 071 072 //处理文件上传的post提交 073 if (request.isMultipartFormData()) { 074 if (ArrayUtil.isNotEmpty(request.getParams())) { 075 uploadByMultipart(request, connection); 076 } 077 } 078 079 //处理正常的post提交 080 else { 081 String uploadBodyString = request.getUploadBodyString(); 082 if (StrUtil.isNotEmpty(uploadBodyString)) { 083 byte[] bytes = uploadBodyString.getBytes(request.getCharset()); 084 085 if (StrUtil.isBlank(request.getHeader("Content-Length"))) { 086 connection.setRequestProperty("Content-Length", String.valueOf(bytes.length)); 087 } 088 089 try (OutputStream outStream = connection.getOutputStream();) { 090 outStream.write(bytes); 091 outStream.flush(); 092 } 093 } 094 } 095 } 096 097 //get 请求 098 else { 099 connection.connect(); 100 } 101 102 int responseCode = connection.getResponseCode(); 103 104 //自动重定向 105 if (responseCode >= 300 && responseCode < 400 && request.isAutoRedirect()) { 106 processRedirect(request, response, connection); 107 return; 108 } 109 110 111 inStream = getInputStream(connection, responseCode); 112 113 response.setContentType(connection.getContentType()); 114 response.setResponseCode(connection.getResponseCode()); 115 response.setHeaders(connection.getHeaderFields()); 116 117 //是否要读取 body 数据 118 if (request.isReadBody()) { 119 response.copyStream(inStream); 120 } 121 122 } catch (Throwable ex) { 123 response.setError(ex); 124 LOG.error(ex.toString(), ex); 125 } finally { 126 127 if (connection != null) { 128 connection.disconnect(); 129 } 130 131 QuietlyUtil.closeQuietly(inStream, response); 132 } 133 } 134 135 136 /** 137 * 手动重定向 138 * 139 * @param request 140 * @param response 141 * @param connection 142 */ 143 private void processRedirect(JbootHttpRequest request, JbootHttpResponse response, HttpURLConnection connection) throws IOException { 144 if (request.getCurrentRedirectCount() > request.getMaxRedirectCount()) { 145 throw new IOException("Exceeded redirect count."); 146 } 147 148 149 String location = connection.getHeaderField("Location"); 150 request.setCurrentRedirectCount(request.getCurrentRedirectCount() + 1); 151 152 //绝对路径 153 if (location.startsWith("/")) { 154 int firstSlash = request.getRequestUrl().indexOf("/", 8); // 8 == "https://".length() 155 location = request.getRequestUrl().substring(0, firstSlash) + location; 156 } 157 158 //相对路径 159 else if (!location.toLowerCase().startsWith("http")) { 160 int lastSlash = request.getRequestUrl().lastIndexOf("/"); 161 location = request.getRequestUrl().substring(0, lastSlash + 1) + location; 162 } 163 164 //携带 cookie 165 String responseCookieString = connection.getHeaderField("Set-Cookie"); 166 if (StrUtil.isNotBlank(responseCookieString)) { 167 List<HttpCookie> cookies = HttpCookie.parse(responseCookieString); 168 StringBuilder cookie = new StringBuilder(StrUtil.obtainDefault(request.getHeader("Cookie"), "")); 169 for (HttpCookie httpCookie : cookies) { 170 cookie.append(httpCookie.getName()).append("=").append(httpCookie.getValue()).append("; "); 171 } 172 request.addHeader("Cookie", cookie.toString()); 173 } 174 175 request.setRequestUrl(location); 176 request.setMethod(JbootHttpRequest.METHOD_GET); 177 178 doProcess(request, response); 179 } 180 181 182 private InputStream getInputStream(HttpURLConnection connection, int responseCode) throws IOException { 183 InputStream stream = responseCode >= 400 ? connection.getErrorStream() : connection.getInputStream(); 184 if ("gzip".equalsIgnoreCase(connection.getContentEncoding())) { 185 return new GZIPInputStream(stream); 186 } else { 187 return stream; 188 } 189 190 } 191 192 193 private void uploadByMultipart(JbootHttpRequest request, HttpURLConnection connection) throws IOException { 194 String endFlag = "\r\n"; 195 String startFlag = "--"; 196 String boundary = "------" + StrUtil.uuid(); 197 connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); 198 DataOutputStream dos = new DataOutputStream(connection.getOutputStream()); 199 for (Map.Entry entry : request.getParams().entrySet()) { 200 if (entry.getValue() instanceof File) { 201 File file = (File) entry.getValue(); 202 checkFileNormal(file); 203 writeString(dos, request, startFlag + boundary + endFlag); 204 writeString(dos, request, "Content-Disposition: form-data; name=\"" + entry.getKey() + "\"; filename=\"" + file.getName() + "\""); 205 writeString(dos, request, endFlag + "Content-Type: " + HttpMimeTypes.getMimeType(file.getName())); 206 writeString(dos, request, endFlag + endFlag); 207 208 writeFile(dos, file); 209 210 writeString(dos, request, endFlag); 211 } else { 212 writeString(dos, request, startFlag + boundary + endFlag); 213 writeString(dos, request, "Content-Disposition: form-data; name=\"" + entry.getKey() + "\""); 214 writeString(dos, request, endFlag + endFlag); 215 writeString(dos, request, String.valueOf(entry.getValue())); 216 writeString(dos, request, endFlag); 217 } 218 } 219 220 writeString(dos, request, startFlag + boundary + startFlag + endFlag); 221 dos.flush(); 222 } 223 224 private void writeString(DataOutputStream dos, JbootHttpRequest request, String s) throws IOException { 225 dos.write(s.getBytes(request.getCharset())); 226 } 227 228 private void writeFile(DataOutputStream dos, File file) throws IOException { 229 try (FileInputStream fStream = new FileInputStream(file)) { 230 byte[] buffer = new byte[2028]; 231 for (int len = 0; (len = fStream.read(buffer)) > 0; ) { 232 dos.write(buffer, 0, len); 233 } 234 } 235 } 236 237 private static void checkFileNormal(File file) { 238 if (!file.exists()) { 239 throw new JbootException("file not exists!!!!" + file); 240 } 241 if (file.isDirectory()) { 242 throw new JbootException("cannot upload directory!!!!" + file); 243 } 244 if (!file.canRead()) { 245 throw new JbootException("cannnot read file!!!" + file); 246 } 247 } 248 249 250 private static void configConnection(HttpURLConnection connection, JbootHttpRequest request) throws ProtocolException { 251 if (connection == null) { 252 return; 253 } 254 connection.setReadTimeout(request.getReadTimeOut()); 255 connection.setConnectTimeout(request.getConnectTimeOut()); 256 connection.setRequestMethod(request.getMethod()); 257 connection.setInstanceFollowRedirects(request.isInstanceFollowRedirects()); 258 259 //如果 reqeust 的 header 不配置 content-Type, 使用默认的 260 connection.setRequestProperty("Content-Type", request.getContentType()); 261 262 if (request.getHeaders() != null && request.getHeaders().size() > 0) { 263 for (Map.Entry<String, String> entry : request.getHeaders().entrySet()) { 264 connection.setRequestProperty(entry.getKey(), entry.getValue()); 265 } 266 } 267 } 268 269 private static HttpURLConnection getConnection(JbootHttpRequest request) throws Exception { 270 271 //get 请求 或者 带有body内容的 post 请求,需要在 url 追加参数 272 if (!request.isPostOrPutRequest() || request.getBodyContent() != null) { 273 request.appendParasToUrl(); 274 } 275 276 return request.isHttps() ? getHttpsConnection(request) : getHttpConnection(request); 277 } 278 279 private static HttpURLConnection getHttpConnection(JbootHttpRequest request) throws Exception { 280 URL url = new URL(request.getRequestUrl()); 281 HttpURLConnection conn = (HttpURLConnection) (request.getProxy() != null 282 ? url.openConnection(request.getProxy()) : url.openConnection()); 283 return conn; 284 } 285 286 private static HttpsURLConnection getHttpsConnection(JbootHttpRequest request) throws Exception { 287 URL url = new URL(request.getRequestUrl()); 288 HttpsURLConnection conn = (HttpsURLConnection) (request.getProxy() != null 289 ? url.openConnection(request.getProxy()) : url.openConnection()); 290 291 //自定义 sslContext 292 if (request.getSslContext() != null) { 293 SSLSocketFactory ssf = request.getSslContext().getSocketFactory(); 294 conn.setSSLSocketFactory(ssf); 295 } 296 //配置证书的路径和密码 297 else if (request.getCertPath() != null && request.getCertPass() != null) { 298 299 KeyStore clientStore = KeyStore.getInstance("PKCS12"); 300 clientStore.load(request.getCertInputStream(), request.getCertPass().toCharArray()); 301 302 KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 303 keyManagerFactory.init(clientStore, request.getCertPass().toCharArray()); 304 KeyManager[] keyManagers = keyManagerFactory.getKeyManagers(); 305 306 307 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 308 trustManagerFactory.init(clientStore); 309 310 SSLContext sslContext = SSLContext.getInstance("TLS"); 311 sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom()); 312 313 conn.setSSLSocketFactory(sslContext.getSocketFactory()); 314 315 } 316 // 默认的 sslContext 317 else { 318 conn.setHostnameVerifier(hnv); 319 SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); 320 if (sslContext != null) { 321 TrustManager[] tm = {trustAnyTrustManager}; 322 sslContext.init(null, tm, null); 323 SSLSocketFactory ssf = sslContext.getSocketFactory(); 324 conn.setSSLSocketFactory(ssf); 325 } 326 } 327 return conn; 328 } 329 330 private static X509TrustManager trustAnyTrustManager = new X509TrustManager() { 331 @Override 332 public void checkClientTrusted(X509Certificate[] chain, String authType) { 333 } 334 335 @Override 336 public void checkServerTrusted(X509Certificate[] chain, String authType) { 337 } 338 339 @Override 340 public X509Certificate[] getAcceptedIssuers() { 341 return null; 342 } 343 }; 344 345 private static HostnameVerifier hnv = (hostname, session) -> true; 346 347 348}