package org.voovan.http.client; import org.voovan.http.message.Request; import org.voovan.http.message.Response; import org.voovan.http.message.packet.Cookie; import org.voovan.http.message.packet.Header; import org.voovan.http.message.packet.Part; import org.voovan.http.websocket.WebSocketFrame; import org.voovan.http.websocket.WebSocketRouter; import org.voovan.network.IoSession; import org.voovan.network.SSLManager; import org.voovan.network.aio.AioSocket; import org.voovan.network.exception.ReadMessageException; import org.voovan.network.exception.SendMessageException; import org.voovan.network.messagesplitter.HttpMessageSplitter; import org.voovan.tools.TEnv; import org.voovan.tools.TFile; import org.voovan.tools.TString; import org.voovan.tools.log.Logger; import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.security.NoSuchAlgorithmException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * HTTP 请求调用 * @author helyho * * Voovan Framework. * WebSite: https://github.com/helyho/Voovan * Licence: Apache v2 License */ public class HttpClient { private AioSocket socket; private Request request; private Map<String, Object> parameters; private String charset="UTF-8"; private String urlString; private boolean isSSL = false; private boolean isWebSocket = false; /** * 构建函数 * @param urlString 请求的 URL 地址 */ public HttpClient(String urlString) { this.urlString = urlString; init(urlString,5); } /** * 构建函数 * @param urlString 请求的 URL 地址 * @param timeOut 超时时间 */ public HttpClient(String urlString,int timeOut) { this.urlString = urlString; init(urlString,timeOut); } /** * 构建函数 * @param urlString 请求的 URL 地址 * @param charset 字符集 * @param timeOut 超时时间 */ public HttpClient(String urlString,String charset,int timeOut) { this.urlString = urlString; this.charset = charset; init(urlString,timeOut); } /** * 构建函数 * @param urlString 请求的 URL 地址 * @param charset 字符集 */ public HttpClient(String urlString,String charset) { this.urlString = urlString; this.charset = charset; init(urlString,5); } private boolean trySSL(String urlString){ boolean isSSL = urlString.toLowerCase().startsWith("https://"); if(!isSSL){ isSSL = urlString.toLowerCase().startsWith("wss://"); } return isSSL; } /** * 初始化函数 * @param urlString 主机地址 * @param timeOut 超时时间 */ private void init(String urlString,int timeOut){ try { isSSL = trySSL(urlString); String hostString = urlString; int port = 80; if(hostString.toLowerCase().startsWith("ws")){ hostString = "http"+hostString.substring(2,hostString.length()); } if(hostString.toLowerCase().startsWith("http")){ URL url = new URL(hostString); hostString = url.getHost(); port = url.getPort(); } if(port==-1 && !isSSL){ port = 80; }else if(port==-1 && isSSL){ port = 443; } parameters = new HashMap<String, Object>(); request = new Request(); //初始化请求参数,默认值 request.header().put("Host", hostString); request.header().put("Pragma", "no-cache"); request.header().put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"); request.header().put("User-Agent", "Voovan Http Client"); request.header().put("Accept-Encoding","gzip"); request.header().put("Connection","keep-alive"); socket = new AioSocket(hostString, port==-1?80:port, timeOut*1000); socket.filterChain().add(new HttpClientFilter()); socket.messageSplitter(new HttpMessageSplitter()); if(isSSL){ try { SSLManager sslManager = new SSLManager("TLS"); socket.setSSLManager(sslManager); } catch (NoSuchAlgorithmException e) { Logger.error(e); } } socket.syncStart(); } catch (IOException e) { Logger.error("HttpClient init error. ",e); } } /** * 读取流 * @return 字节缓冲对象ByteBuffer * @throws IOException IO异常对象 */ public ByteBuffer loadStream() throws IOException { IoSession session = socket.getSession(); ByteBuffer tmpBuffer = ByteBuffer.allocate(socket.getBufferSize()); session.enabledMessageSpliter(false); int readSize = session.read(tmpBuffer); if(session.getAttribute("SocketException") instanceof Exception){ session.close(); return null; }else if(readSize > 0) { return tmpBuffer; } else if(readSize == 0){ tmpBuffer.flip(); }else if(readSize == -1){ return null; } return tmpBuffer; } /** * 设置请求方法 * @param method Http 请求的方法 * @return HttpClient 对象 */ public HttpClient setMethod(String method){ request.protocol().setMethod(method); return this; } /** * 设置报文形式 * @param bodyType Http 报文形式 * @return Request.BodyType 枚举 */ public HttpClient setBodyType(Request.RequestType bodyType){ //如果之前设置过 ContentType 则不自动设置 ContentType if(!request.header().contain("Content-Type")) { if (bodyType == Request.RequestType.BODY_MULTIPART) { request.header().put("Content-Type", "multipart/form-data;"); } else if (bodyType == Request.RequestType.BODY_URLENCODED) { request.header().put("Content-Type", "application/x-www-form-urlencoded"); } } return this; } /** * 设置请求内容 * @param data 请求内容 * @return HttpClient 对象 */ public HttpClient setData(byte[] data){ if(data!=null) { request.body().write(data); } return this; } /** * 设置请求内容 * @param data 请求内容 * @return HttpClient 对象 */ public HttpClient setData(String data){ if(data!=null) { request.body().write(data); } return this; } /** * 设置请求内容 * @param data 请求内容 * @param charset 字符集 * @return HttpClient 对象 */ public HttpClient setData(String data, String charset){ if(data!=null) { request.body().write(data, charset); } return this; } /** * 获取请求头集合 * @return Header 对象 */ public Header getHeader(){ return request.header(); } /** * 设置请求头 * @param name Header 名称 * @param value Header 值 * @return HttpClient 对象 */ public HttpClient putHeader(String name ,String value){ request.header().put(name, value); return this; } /** * 获取Cookie集合 * @return Cookie集合 */ public List<Cookie> getCookies(){ return request.cookies(); } /** * 获取请求参数集合 * @return 参数对象 */ public Map<String,Object> getParameters(){ return parameters; } /** * 设置POST多段请求 * 类似 Form 的 Actiong="POST" enctype="multipart/form-data" * @param part POST 请求包文对象 * @return HttpClient 对象 */ public HttpClient addPart(Part part){ request.parts().add(part); return this; } /** * 设置请求参数 * @param name 参数名 * @param value 参数值 * @return HttpClient 对象 */ public HttpClient putParameters(String name,Object value){ parameters.put(name, value); return this; } /** * 构建QueryString * 将 Map 集合转换成 QueryString 字符串 * @param parameters 用于保存拼装请求字符串参数的 Map 对象 * @param charset 参数的字符集 * @return 请求字符串 */ public static String buildQueryString(Map<String,Object> parameters,String charset){ String queryString = ""; StringBuilder queryStringBuilder = new StringBuilder(); try { for (Entry<String, Object> parameter : parameters.entrySet()) { queryStringBuilder.append(parameter.getKey()); queryStringBuilder.append("="); queryStringBuilder.append(URLEncoder.encode(parameter.getValue().toString(), charset)); queryStringBuilder.append("&"); } queryString = queryStringBuilder.toString(); queryString = queryStringBuilder.length()>0?TString.removeSuffix(queryString):queryString; } catch (IOException e) { Logger.error("HttpClient getQueryString error. ",e); } return queryString.isEmpty()? "" : queryString; } /** * 构建QueryString * 将 Map 集合转换成 QueryString 字符串 * @return 请求字符串 */ private String getQueryString(){ return buildQueryString(parameters,charset); } /** * 构建请求 */ private void buildRequest(String urlString){ request.protocol().setPath(urlString.isEmpty()?"/":urlString); //1.没有报文 Body,参数包含于请求URL if (request.getBodyType() == Request.RequestType.NORMAL) { String queryString = getQueryString(); String requestPath = request.protocol().getPath(); if(requestPath.contains("?")){ queryString = "&"+queryString; }else{ queryString = "?"+queryString; } request.protocol().setPath(request.protocol().getPath() + queryString); } //2.请求报文Body 使用Part 类型 else if(request.getBodyType() == Request.RequestType.BODY_MULTIPART){ try{ for (Entry<String, Object> parameter : parameters.entrySet()) { Part part = new Part(); part.header().put("name", parameter.getKey()); if(parameter.getValue() instanceof String) { part.body().write(URLEncoder.encode(parameter.getValue().toString(), charset).getBytes(charset)); }else if(parameter.getValue() instanceof File){ part.body().write(TFile.loadFile((File) parameter.getValue())); } request.parts().add(part); } } catch (IOException e) { Logger.error("HttpClient buildRequest error. ",e); } } //3.请求报文Body 使用流类型 else if(request.getBodyType() == Request.RequestType.BODY_URLENCODED){ String queryString = getQueryString(); request.body().write(queryString,charset); } } /** * 连接并发送请求 * @param urlString 请求 URL * @return Response 对象 * @throws SendMessageException 发送异常 * @throws ReadMessageException 读取异常 */ public Response send(String urlString) throws SendMessageException, ReadMessageException { if(isWebSocket){ throw new SendMessageException("The WebSocket is connect, you can't send http request."); } //设置默认的报文 Body 类型 if(request.protocol().getMethod().equals("POST") && request.parts().size()>0){ setBodyType(Request.RequestType.BODY_MULTIPART); }else if(request.protocol().getMethod().equals("POST") && getParameters().size()>0){ setBodyType(Request.RequestType.BODY_URLENCODED); }else{ setBodyType(Request.RequestType.NORMAL); } //构造 Request 对象 buildRequest(TString.isNullOrEmpty(urlString)?"/":urlString); //发送报文 socket.synchronouSend(request); Object readObject = socket.synchronouRead(); Response response = null; //如果是异常则抛出异常 if(readObject instanceof Exception){ throw new ReadMessageException((Exception) readObject); }else{ response = (Response) readObject; } //结束操作 finished(response); return response; } /** * 发送二进制数据 * @param buffer 二进制数据 * @return 发送的字节数 * @throws IOException IO 异常 */ public int send(ByteBuffer buffer) throws IOException { return socket.getSession().send(buffer); } /** * 请求完成 * @param response 请求对象 */ private void finished(Response response){ //传递 cookie 到 Request 对象 if(response!=null && response.cookies()!=null && !response.cookies().isEmpty()){ request.cookies().addAll(response.cookies()); } //清理请求对象,以便下次请求使用 parameters.clear(); request.body().clear(); request.parts().clear(); request.header().remove("Content-Type"); request.header().remove("Content-Length"); request.header().remove("Content-Length"); } /** * 发送行数 * @return Response 对象 * @throws SendMessageException 发送异常 * @throws ReadMessageException 读取异常 */ public Response send() throws SendMessageException, ReadMessageException { return send("/"); } /** * 连接 Websocket * @param urlString URL地址 * @param webSocketRouter WebSocker的路由 * @throws SendMessageException 发送异常 * @throws ReadMessageException 读取异常 */ public void webSocket(String urlString, WebSocketRouter webSocketRouter) throws SendMessageException, ReadMessageException { request.header().put("Connection","Upgrade"); request.header().put("Upgrade", "websocket"); request.header().put("Pragma","no-cache"); request.header().put("Origin", this.urlString); request.header().put("Sec-WebSocket-Version","13"); request.header().put("Sec-WebSocket-Key","c1Mm+c0b28erlzCWWYfrIg=="); Response response = send(urlString); if(response.protocol().getStatus()==101){ isWebSocket = true; //这里需要效验Sec-WebSocket-Accept socket.getSession().setAttribute("Type","WebSocket"); WebSocketHandler webSocketHandler = new WebSocketHandler(this, webSocketRouter); socket.handler(webSocketHandler); //触发 onOpen 方法 webSocketRouter.setSession(socket.getSession()); ByteBuffer buffer = webSocketRouter.onOpen(); if(buffer!=null) { WebSocketFrame webSocketFrame = WebSocketFrame.newInstance(true, WebSocketFrame.Opcode.BINARY, true, buffer); sendData(webSocketFrame); webSocketFrame.getFrameData().flip(); } }else{ //异常发送关闭帧 WebSocketFrame errWebSocketFrame = WebSocketFrame.newInstance(true, WebSocketFrame.Opcode.CLOSING, false, ByteBuffer.wrap(new byte[]{})); sendData(errWebSocketFrame); throw new ReadMessageException("Connect WebSocket error!"); } //为异步调用进行阻赛,等待 socket 关闭 while(socket.isOpen()){ TEnv.sleep(1); } } private void sendData(WebSocketFrame webSocketFrame){ try { socket.getSession().syncSend(webSocketFrame); } catch (SendMessageException e) { Logger.error("WebSocket on connect send data error! "+e.getMessage(), e); } } /** * 关闭 HTTP 连接 */ public void close(){ socket.close(); } /** * 判断是否处于连接状态 * @return 是否连接 */ public boolean isConnect(){ return socket.isConnected(); } }