jodd-http6.3.0源码分析

183 阅读8分钟

简介

jodd-http是一个微型简约的http client,然而简单而且方便,使用它可以轻松的实现发送请求和读取响应,由于个人工作需要,现在对其进行部分源码分析,以6.3.0版本为例

通过maven引入

<dependency>
  <groupId>org.jodd</groupId>
  <artifactId>jodd-http</artifactId>
  <version>6.3.0</version>
</dependency>

例子1

写一个简单的get请求

HttpRequest httpRequest = HttpRequest.get("http://baidu.com");
HttpResponse httpResponse = httpRequest.send();

System.out.println(httpResponse.bodyText());

源码分析1

创建了一个httpRequest对象

public static HttpRequest get(final String destination) {
	return new HttpRequest()
			.method(HttpMethod.GET)
			.set(destination);
}

initRequest

初始化,默认为非长连接,即将headers中的Connection设置为Close,同时将Defaults.headers中的内容复制到this.headers中,即将设置User-Agent为Jodd HTTP

public HttpRequest() {
	initRequest();
}

protected void initRequest() {
    connectionKeepAlive(false);
	if (Defaults.headers.size() > 0) {
		for (Map.Entry<String, String> entry : Defaults.headers) {
			this.headers.add(entry.getKey(), entry.getValue());
		}
	}
}

method

设置http的请求方法

public HttpRequest method(final HttpMethod httpMethod) {
		this.method = httpMethod.name();
		return this;
}

set

只保留核心流程,根据传入参数设置httpRequest的方法,协议,端口,主机,路径,参数

public HttpRequest set(String destination) {

            // 根据传入参数重新设置请求方法
			try {
				final HttpMethod httpMethod = HttpMethod.valueOf(method);
				this.method = httpMethod.name();
				destination = destination.substring(ndx + 1);
			}
			catch (final IllegalArgumentException ignore) {
				// unknown http method
			}
		}

        // 设置请求协议
		if (ndx != -1) {
			protocol = destination.substring(0, ndx);
			destination = destination.substring(ndx + 3);
		}

			String hostToSet = destination.substring(0, ndx);
			destination = destination.substring(ndx);

            // 设置请求的端口
			if (ndx == -1) {
				port = Defaults.DEFAULT_PORT;
			} else {
				port = Integer.parseInt(hostToSet.substring(ndx + 1));
				hostToSet = hostToSet.substring(0, ndx);
			}

            // 设置请求的主机
			host(hostToSet);
		}

        // 设置请求的路径和参数
		path(destination);

		return this;
	}

send

// 默认情况下不需要重定向
public HttpResponse send() {
	if (!followRedirects) {
		return _send();
	}
}

// 一开始没有httpConnection
private HttpResponse _send() {
	if (httpConnection == null) {
		open();
	}

	// sends data
	final HttpResponse httpResponse;
	try {
		final OutputStream outputStream = httpConnection.getOutputStream();

		sendTo(outputStream);

		final InputStream inputStream = httpConnection.getInputStream();

		httpResponse = HttpResponse.readFrom(inputStream);

		httpResponse.assignHttpRequest(this);
	} catch (final IOException ioex) {
		throw new HttpException(ioex);
	}

	final boolean keepAlive = httpResponse.isConnectionPersistent();

	if (!keepAlive) {
		// closes connection if keep alive is false, or if counter reached 0
		httpConnection.close();
		httpConnection = null;
	}

	return httpResponse;
}

open

设置httpRequest的httpConnectionProvider和httpConnection

// 一开始并没有httpConnectionProvider
public HttpRequest open() {
	if (httpConnectionProvider == null) {
		return open(HttpConnectionProvider.get());
	}

	return open(httpConnectionProvider);
}

public HttpRequest open(final HttpConnectionProvider httpConnectionProvider) {
	if (this.httpConnection != null) {
		throw new HttpException("Connection already opened");
	}
	try {
		this.httpConnectionProvider = httpConnectionProvider;
		this.httpConnection = httpConnectionProvider.createHttpConnection(this);
	} catch (final IOException ioex) {
		throw new HttpException("Can't connect to: " + url(), ioex);
	}

	return this;
}

HttpConnectionProvider

实际上是创建了一个SocketHttpConnectionProvider

public interface HttpConnectionProvider {

	class Implementation {
		private static HttpConnectionProvider httpConnectionProvider = new SocketHttpConnectionProvider();

		public static void set(final HttpConnectionProvider httpConnectionProvider) {
			Implementation.httpConnectionProvider = httpConnectionProvider;
		}
	}

    // 返回一个SocketHttpConnectionProvider
	static HttpConnectionProvider get() {
		return Implementation.httpConnectionProvider;
	}

    public HttpConnection createHttpConnection(HttpRequest httpRequest) throws IOException;

}

createHttpConnection

开始创建连接,只保留核心流程:

@Override
public HttpConnection createHttpConnection(final HttpRequest httpRequest) throws IOException {
	final SocketHttpConnection httpConnection;

        // 创建一个socket
		Socket socket = createSocket(httpRequest.host(), httpRequest.port(), httpRequest.connectionTimeout());

        // 创建一个连接并返回
		httpConnection = new SocketHttpConnection(socket);
	
		return httpConnection;
}

createSocket

只保留核心流程,实际上是创建并开启一个socket,核心参数是host和port

protected Socket createSocket(final String host, final int port, final int connectionTimeout) throws IOException {
	final SocketFactory socketFactory = resolveSocketFactory(proxy, false, false, connectionTimeout);

    // 利用工厂创建一个socket并开启
	if (connectionTimeout < 0) {
		return socketFactory.createSocket(host, port);
	}
    // 设置超时时间
	else {
		Socket socket = socketFactory.createSocket();

		socket.connect(new InetSocketAddress(host, port), connectionTimeout);

		return socket;
	}
}

// 默认情况下返回SocketFactory.getDefault()
protected ProxyInfo proxy = ProxyInfo.directProxy();

public static ProxyInfo directProxy() {
	return new ProxyInfo(ProxyType.NONE, null, 0, null, null);
}

protected SocketFactory resolveSocketFactory(
		final ProxyInfo proxy,
		final boolean ssl,
		final boolean trustAllCertificates,
		final int connectionTimeout) throws IOException {

	switch (proxy.getProxyType()) {
		case NONE:
			if (ssl) {
				return getDefaultSSLSocketFactory(trustAllCertificates);
			}
			else {
				return SocketFactory.getDefault();
			}
            
		}
	}

HttpConnection

httpConnection = new SocketHttpConnection(socket),可以发现httpConnection实际上是socket的一个包装类,对外暴露了一些控制方法

public class SocketHttpConnection implements HttpConnection {

	protected final Socket socket;

	public SocketHttpConnection(final Socket socket) {
		this.socket = socket;
	}

	@Override
	public void init() throws IOException {
		if (timeout >= 0) {
			socket.setSoTimeout(timeout);
		}
	}

	@Override
	public OutputStream getOutputStream() throws IOException {
		return socket.getOutputStream();
	}

	@Override
	public InputStream getInputStream() throws IOException {
		return socket.getInputStream();
	}

	@Override
	public void close() {
		try {
			socket.close();
		} catch (Throwable ignore) {
		}
	}

	@Override
	public void setTimeout(final int milliseconds) {
		this.timeout = milliseconds;
	}

	public Socket getSocket() {
		return socket;
	}

	private int timeout;
}

_send

回到_send,从httpConnection也就是socket控制器中拿到outputStream和inputStream,用于获取输入/输出

private HttpResponse _send() {
	if (httpConnection == null) {
		open();
	}

	// sends data
	final HttpResponse httpResponse;
	try {
        // 从socket控制器中拿到outputStream
		final OutputStream outputStream = httpConnection.getOutputStream();

        // 向outputStream中写入内容
		sendTo(outputStream);

        // 从socket控制器中拿到inputStream
		final InputStream inputStream = httpConnection.getInputStream();

        // 拿到httpResponse
		httpResponse = HttpResponse.readFrom(inputStream);
        
        // httpResponse.httpRequest = httpRequest
		httpResponse.assignHttpRequest(this);
	} catch (final IOException ioex) {
		throw new HttpException(ioex);
	}

	final boolean keepAlive = httpResponse.isConnectionPersistent();

    // 如果不是长连接则关闭socket连接
	if (!keepAlive) {
		// closes connection if keep alive is false, or if counter reached 0
		httpConnection.close();
		httpConnection = null;
	}

	return httpResponse;
}

sendTo

先写buffer,再将buffer写入outputStream并发送请求

public void sendTo(final OutputStream out) throws IOException {

    // 创建一个buffer
	final Buffer buffer = buffer(true);

    // 先把buffer中写入outputStream
	if (httpProgressListener == null) {
		buffer.writeTo(out);
	}
	else {
		buffer.writeTo(out, httpProgressListener);
	}

    // 利用outputStream发送请求数据
	out.flush();
}

先构造请求行,再通过populateHeaderAndBody构造请求头和请求体,最后生成完整的requestBuffer

@Override
protected Buffer buffer(final boolean fullRequest) {
	// INITIALIZATION

    // host port

	if (header(HEADER_HOST) == null) {
		setHostHeader();
	}

	// 请求体中的formBuffer
	final Buffer formBuffer = formBuffer();

	// query string

	final String queryString = queryString();

	// POST method requires Content-Type to be set

	if (method.equals("POST") && (contentLength() == null)) {
		contentLength(0);
	}

    // 完整的requestBuffer
	final Buffer request = new Buffer();

    // 设置请求行
	request.append(method)
		.append(SPACE)
		.append(path);

	if (query != null && !query.isEmpty()) {
		request.append('?');
		request.append(queryString);
	}

	request.append(SPACE)
		.append(httpVersion)
		.append(CRLF);

    // 将formBuffer合并到requestBuffer
	populateHeaderAndBody(request, formBuffer, fullRequest);

	return request;
}

protected void populateHeaderAndBody(final Buffer target, final Buffer formBuffer, final boolean fullRequest) {
	for (final String name : headers.names()) {
		final List<String> values = headers.getAll(name);

		final String key = capitalizeHeaderKeys ? HttpUtil.prepareHeaderParameterName(name) : name;

		target.append(key);
		target.append(": ");
		int count = 0;

        // 设置请求头
		for (final String value : values) {
			if (count++ > 0) {
				target.append(", ");
			}
			target.append(value);
		}

			target.append(CRLF);
	}

        // 设置请求体
		if (fullRequest) {
			target.append(CRLF);

			if (form != null) {
				target.append(formBuffer);
			} else if (body != null) {
				target.append(body);
			}
		}
}

Buffer

Buffer是一种特殊的链表每次我们的写入都是操作链表末尾的缓冲区,writeTo将会buffer中的内容复制到outputStream中,先写buffer再写outputStream减少了网络IO

public class Buffer {

    // 链表结构
	protected LinkedList<Object> list = new LinkedList<>();
    
    // 链表末尾的缓冲区
	protected ByteArrayOutputStream last;

    // 链表长度
	protected int size;

    // 每次写入都是操作链表末尾的缓冲区
	public Buffer append(final String string) {
		ensureLast();

		final byte[] bytes = string.getBytes(StandardCharsets.ISO_8859_1);
		last.write(bytes, 0, bytes.length);
		size += bytes.length;

		return this;
	}

    private void ensureLast() {
		if (last == null) {
			last = new ByteArrayOutputStream();
			list.add(last);
		}
	}

    // 将buffer中的内容复制到outputStream中
	public void writeTo(final OutputStream out) throws IOException {
		for (final Object o : list) {
			if (o instanceof ByteArrayOutputStream) {
				final ByteArrayOutputStream arrays = (ByteArrayOutputStream) o;
				out.write(arrays.toByteArray());
			}
			else if (o instanceof Uploadable) {
				final Uploadable uploadable = (Uploadable) o;

				final InputStream inputStream = uploadable.openInputStream();

				try {
					IOUtil.copy(inputStream, out);
				}
				finally {
					IOUtil.close(inputStream);
				}
			}
		}
	}
    
}

回到_send,看看拿到httpResponse的流程

private HttpResponse _send() {
	if (httpConnection == null) {
		open();
	}

	// sends data
	final HttpResponse httpResponse;
	try {
        // 从socket控制器中拿到outputStream
		final OutputStream outputStream = httpConnection.getOutputStream();

        // 向outputStream中写入内容
		sendTo(outputStream);

        // 从socket控制器中拿到inputStream
		final InputStream inputStream = httpConnection.getInputStream();

        // 拿到httpResponse
		httpResponse = HttpResponse.readFrom(inputStream);
        
        // httpResponse.httpRequest = httpRequest
		httpResponse.assignHttpRequest(this);
	} catch (final IOException ioex) {
		throw new HttpException(ioex);
	}

	final boolean keepAlive = httpResponse.isConnectionPersistent();

    // 如果不是长连接则关闭socket连接
	if (!keepAlive) {
		// closes connection if keep alive is false, or if counter reached 0
		httpConnection.close();
		httpConnection = null;
	}

	return httpResponse;
}

readFrom

httpResponse = HttpResponse.readFrom(inputStream),只保留核心流程,实际上就是通过读取socket连接的inputStreamReader,创建httpResponse并设置状态码,状态短语,响应头,响应体

public static HttpResponse readFrom(final InputStream in) {
    final InputStreamReader inputStreamReader = new InputStreamReader(in, StandardCharsets.ISO_8859_1);
    final BufferedReader reader = new BufferedReader(inputStreamReader);

    final HttpResponse httpResponse = new HttpResponse();

    // the first line
    String line;
    try {
        line = reader.readLine();
    } catch (final IOException ioex) {
        throw new HttpException(ioex);
    }

    // 设置状态码
    try {
        httpResponse.statusCode(Integer.parseInt(line.substring(ndx, ndx2).trim()));
    }
    catch (final NumberFormatException nfex) {
        httpResponse.statusCode(-1);
    }

    // 设置状态短语 
    httpResponse.statusPhrase(line.substring(ndx2).trim());
}

// 设置响应头 
httpResponse.readHeaders(reader);

// 设置响应体
httpResponse.readBody(reader);

return httpResponse;
}

至此已经成功的获取到了httpResponse

bodyText

返回httpResponse的body

public String bodyText() {
    if (body == null) {
		return StringPool.EMPTY;
	}
	if (charset != null) {
		return StringUtil.convertCharset(body, StandardCharsets.ISO_8859_1, Charset.forName(charset));
	}
	return bodyRaw();
}

public String bodyRaw() {
	return body;
}

例子2

写一个简单的session

HttpRequest httpRequest = HttpRequest.get("http://baidu.com");
HttpSession httpSession = new HttpSession();

httpSession.sendRequest(httpRequest);
String page = httpSession.getPage();

System.out.println(page);

源码分析2

HttpSession

创建了httpConnectionProvider,维护全局的cookies和heards,默认处理http传输过程中的异常和重定向

public class HttpSession {

	protected HttpConnectionProvider httpConnectionProvider;
	
    protected HttpRequest httpRequest;
	protected HttpResponse httpResponse;
    
	protected HttpMultiMap<Cookie> cookies = HttpMultiMap.newCaseInsensitiveMap();
    protected HeadersMultiMap defaultHeaders = new HeadersMultiMap();
	
    protected boolean keepAlive;
    
    // 上一次发送httpRequest到返回httpResponse所用的时间
	protected long elapsedTime;
    // 是否处理http传输过程中的异常
	protected boolean catchTransportExceptions = true;
    // 是否处理重定向
	protected boolean handleRedirects = true;

	public HttpSession() {
		httpConnectionProvider = HttpConnectionProvider.get();
	}

}

sendRequest

只保留核心流程,发现session是通过维护全局的heards和cookies,保存多次请求和响应的信息实现的

public HttpResponse sendRequest(HttpRequest httpRequest) {
	elapsedTime = System.currentTimeMillis();

	while (true) {
		this.httpRequest = httpRequest;
		final HttpResponse previousResponse = this.httpResponse;
		this.httpResponse = null;

        // 读取httpSession,为httpRequest设置最新的headers和cookies
		addDefaultHeaders(httpRequest);
		addCookies(httpRequest);


		// send request
		if (catchTransportExceptions) {
			try {
				this.httpResponse = _sendRequest(httpRequest, previousResponse);
			}
			catch (final HttpException httpException) {
    			    // 处理http传输过程中的异常
			}
		}
		else {
			this.httpResponse =_sendRequest(httpRequest, previousResponse);
		}

        // 读取httpResponse,为httpSession设置最新的headers和cookies
		readCookies(httpResponse);

        // 如果不需要处理重定向则直接退出
		if (!handleRedirects) {
			break;
		}

		final int statusCode = httpResponse.statusCode();

        // 处理重定向的情况

		// 301: moved permanently
		if (statusCode == 301) {
			final String newPath = httpResponse.location();

			if (newPath == null) {
				break;
			}

			httpRequest = HttpRequest.get(newPath);
			continue;
		}

		// 302: redirect, 303: see other
		if (statusCode == 302 || statusCode == 303) {
			final String newPath = httpResponse.location();

			if (newPath == null) {
				break;
			}

			httpRequest = HttpRequest.get(newPath);
			continue;
		}

		// 307: temporary redirect, 308: permanent redirect
		if (statusCode == 307 || statusCode == 308) {
			final String newPath = httpResponse.location();

			if (newPath == null) {
				break;
			}

			final String originalMethod = httpRequest.method();
			final String originalBody = httpRequest.bodyRaw();
			httpRequest = new HttpRequest()
					.method(originalMethod)
					.set(newPath)
					.body(originalBody);
            
			continue;
		}

		break;
	}

	elapsedTime = System.currentTimeMillis() - elapsedTime;

	return this.httpResponse;
}

addDefaultHeaders

将httpSession的defaultHeaders中存在而httpRequest.headers中不存在的内容添加到httpRequest.headers中

protected void addDefaultHeaders(final HttpRequest httpRequest) {
	for (final Map.Entry<String, String> entry : defaultHeaders.entries()) {
		final String name = entry.getKey();

		if (!httpRequest.headers.contains(name)) {
			httpRequest.headers.add(name, entry.getValue());
		}
	}
}

addCookies

先将httpRequest中的cookies信息更新到httpSession中,再令当前httpRequest的cookies信息与httpSession中的cookies信息保持一致

protected void addCookies(final HttpRequest httpRequest) {
		
	final List<Cookie> cookiesList = new ArrayList<>();

	for (final Cookie cookie : httpRequest.cookies()) {
		cookies.set(cookie.getName(),cookie);
	}

	if (!cookies.isEmpty()) {
		for (final Map.Entry<String, Cookie> cookieEntry : cookies) {
			cookiesList.add(cookieEntry.getValue());
		}

		httpRequest.cookies(cookiesList.toArray(new Cookie[0]));
	}
}

// 从headers的cookie中拿出对应信息
public Cookie[] cookies() {
	final String cookieHeader = header("cookie");
	if (!StringUtil.isNotBlank(cookieHeader)) {
		return new Cookie[0];
	}

	return Arrays
		.stream(StringUtil.splitc(cookieHeader, ';'))
		.map(Cookie::new)
		.toArray(Cookie[]::new);
}

// 将有效的cookies设置到headers中
public HttpRequest cookies(final Cookie... cookies) {
	if (cookies.length == 0) {
		return this;
	}

	final StringBuilder cookieString = new StringBuilder();

	boolean first = true;

	for (final Cookie cookie : cookies) {
		final Integer maxAge = cookie.getMaxAge();
		if (maxAge != null && maxAge == 0) {
			continue;
		}

		if (!first) {
			cookieString.append("; ");
		}

		first = false;
		cookieString.append(cookie.getName());
		cookieString.append('=');
		cookieString.append(cookie.getValue());
	}

	headerOverwrite("cookie", cookieString.toString());

	return this;
}

_sendRequest

默认情况下会走例子1中open和send的流程,拿到httpResponse

protected HttpResponse _sendRequest(final HttpRequest httpRequest, final HttpResponse previousResponse) {
	
    // 默认情况下keepAlive为false
    if (!keepAlive) {
        // 打开httpConnection
		httpRequest.open(httpConnectionProvider);
	} else {
		// keeping alive
		if (previousResponse == null) {
			httpRequest.open(httpConnectionProvider).connectionKeepAlive(true);
		} else {
			httpRequest.keepAlive(previousResponse, true);
		}
	}

    // 发送httpRequest返回httpResponse
	return httpRequest.send();
}

readCookies

将httpResponse中的cookies信息更新到httpSession中

protected void readCookies(final HttpResponse httpResponse) {
	final Cookie[] newCookies = httpResponse.cookies();

	for (final Cookie cookie : newCookies) {
		cookies.set(cookie.getName(), cookie);
	}
}

public Cookie[] cookies() {
	final List<String> newCookies = headers("set-cookie");

	if (newCookies == null) {
		return new Cookie[0];
	}

	final List<Cookie> cookieList = new ArrayList<>(newCookies.size());

	for (final String cookieValue : newCookies) {
		try {
			final Cookie cookie = new Cookie(cookieValue);

			cookieList.add(cookie);
		}
		catch (final Exception ex) {
			// ignore
		}
	}

	return cookieList.toArray(new Cookie[0]);
}

getPage

返回httpResponse的body

public String getPage() {
	if (httpResponse == null) {
		return null;
	}
	return httpResponse.bodyText();
}