All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jodd.http.HttpRequest Maven / Gradle / Ivy

There is a newer version: 6.3.0
Show newest version
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.http;

import jodd.net.HttpMethod;
import jodd.net.MimeTypes;
import jodd.util.Base64;
import jodd.util.StringBand;
import jodd.util.StringPool;
import jodd.util.StringUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Function;

import static jodd.util.StringPool.CRLF;
import static jodd.util.StringPool.SPACE;

/**
 * HTTP request.
 */
public class HttpRequest extends HttpBase {

	protected String protocol = "http";
	protected String host = "localhost";
	protected int port = Defaults.DEFAULT_PORT;
	protected String method = "GET";
	protected String path = StringPool.SLASH;
	protected HttpMultiMap query;

	// ---------------------------------------------------------------- init

	public HttpRequest() {
		initRequest();
	}

	/**
	 * Prepares request on creation. By default, it just
	 * adds "Connection: Close" header.
	 */
	protected void initRequest() {
		connectionKeepAlive(false);
	}

	// ---------------------------------------------------------------- properties

	/**
	 * Returns request host name.
	 */
	public String host() {
		return host;
	}

	/**
	 * Sets request host name.
	 */
	public HttpRequest host(final String host) {
		this.host = host;
		if (headers.contains(HEADER_HOST)) {
			headerOverwrite(HEADER_HOST, host);
		}
		return this;
	}

	/**
	 * Returns used protocol. By default it's "http".
	 */
	public String protocol() {
		return protocol;
	}

	/**
	 * Defines protocol.
	 */
	public HttpRequest protocol(final String protocol) {
		this.protocol = protocol;
		return this;
	}

	/**
	 * Returns request port number. When port is not
	 * explicitly defined, returns default port for
	 * current protocol.
	 */
	public int port() {
		if (port == Defaults.DEFAULT_PORT) {
			if (protocol == null) {
				return 80;
			}
			if (protocol.equalsIgnoreCase("https")) {
				return 443;
			}
			return 80;
		}
		return port;
	}

	/**
	 * Sets request port number.
	 */
	public HttpRequest port(final int port) {
		this.port = port;
		return this;
	}

	// ---------------------------------------------------------------- set

	/**
	 * Sets the destination (method, host, port... ) at once.
	 */
	public HttpRequest set(String destination) {
		destination = destination.trim();

		// http method, optional

		int ndx = destination.indexOf(' ');

		if (ndx != -1) {
			String method = destination.substring(0, ndx).toUpperCase();

			try {
				HttpMethod httpMethod = HttpMethod.valueOf(method);
				this.method = httpMethod.name();
				destination = destination.substring(ndx + 1);
			}
			catch (IllegalArgumentException ignore) {
				// unknown http method
			}
		}

		// protocol

		ndx = destination.indexOf("://");

		if (ndx != -1) {
			protocol = destination.substring(0, ndx);
			destination = destination.substring(ndx + 3);
		}

		// host

		ndx = destination.indexOf('/');

		if (ndx == -1) {
			ndx = destination.length();
		}

		if (ndx != 0) {

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

			// port

			ndx = hostToSet.indexOf(':');

			if (ndx == -1) {
				port = Defaults.DEFAULT_PORT;
			} else {
				port = Integer.parseInt(hostToSet.substring(ndx + 1));
				hostToSet = hostToSet.substring(0, ndx);
			}

			host(hostToSet);
		}

		// path + query

		path(destination);

		return this;
	}

	// ---------------------------------------------------------------- static factories

	/**
	 * Generic request builder, usually used when method is a variable.
	 * Otherwise, use one of the other static request builder methods.
	 */
	public static HttpRequest create(final String method, final String destination) {
		return new HttpRequest()
				.method(method.toUpperCase())
				.set(destination);
	}

	/**
	 * Builds a CONNECT request.
	 */
	public static HttpRequest connect(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.CONNECT)
				.set(destination);
	}
	/**
	 * Builds a GET request.
	 */
	public static HttpRequest get(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.GET)
				.set(destination);
	}
	/**
	 * Builds a POST request.
	 */
	public static HttpRequest post(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.POST)
				.set(destination);
	}
	/**
	 * Builds a PUT request.
	 */
	public static HttpRequest put(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.PUT)
				.set(destination);
	}
	/**
	 * Builds a PATCH request.
	 */
	public static HttpRequest patch(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.PATCH)
				.set(destination);
	}
	/**
	 * Builds a DELETE request.
	 */
	public static HttpRequest delete(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.DELETE)
				.set(destination);
	}
	/**
	 * Builds a HEAD request.
	 */
	public static HttpRequest head(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.HEAD)
				.set(destination);
	}
	/**
	 * Builds a TRACE request.
	 */
	public static HttpRequest trace(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.TRACE)
				.set(destination);
	}
	/**
	 * Builds an OPTIONS request.
	 */
	public static HttpRequest options(final String destination) {
		return new HttpRequest()
				.method(HttpMethod.OPTIONS)
				.set(destination);
	}

	// ---------------------------------------------------------------- request

	/**
	 * Returns request method.
	 */
	public String method() {
		return method;
	}

	/**
	 * Specifies request method. It will be converted into uppercase.
	 * Does not validate if method is one of the HTTP methods.
	 */
	public HttpRequest method(final String method) {
		this.method = method.toUpperCase();
		return this;
	}
	public HttpRequest method(final HttpMethod httpMethod) {
		this.method = httpMethod.name();
		return this;
	}

	/**
	 * Returns request path, without the query.
	 */
	public String path() {
		return path;
	}

	/**
	 * Sets request path. Query string is allowed.
	 * Adds a slash if path doesn't start with one.
	 * Query will be stripped out from the path.
	 * Previous query is discarded.
	 * @see #query()
	 */
	public HttpRequest path(String path) {
		// this must be the only place that sets the path

		if (!path.startsWith(StringPool.SLASH)) {
			path = StringPool.SLASH + path;
		}

		int ndx = path.indexOf('?');

		if (ndx != -1) {
			String queryString = path.substring(ndx + 1);

			path = path.substring(0, ndx);

			query = HttpUtil.parseQuery(queryString, true);
		} else {
			query = HttpMultiMap.newCaseInsensitiveMap();
		}

		this.path = path;

		return this;
	}

	/**
	 * Forces multipart requests. When set to false,
	 * it will be {@link #isFormMultipart() detected} if request
	 * should be multipart. By setting this to true
	 * we are forcing usage of multipart request.
	 */
	public HttpRequest multipart(final boolean multipart) {
		this.multipart = multipart;
		return this;
	}


	// ---------------------------------------------------------------- cookies

	/**
	 * Sets cookies to the request.
	 */
	public HttpRequest cookies(final Cookie... cookies) {
		if (cookies.length == 0) {
			return this;
		}

		StringBuilder cookieString = new StringBuilder();

		boolean first = true;

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

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

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

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

		return this;
	}


	// ---------------------------------------------------------------- query

	/**
	 * Adds query parameter.
	 */
	public HttpRequest query(final String name, final String value) {
		query.add(name, value);
		return this;
	}

	/**
	 * Adds many query parameters at once. Although it accepts objects,
	 * each value will be converted to string.
	 */
	public HttpRequest query(final String name1, final Object value1, final Object... parameters) {
		query(name1, value1 == null ? null : value1.toString());

		for (int i = 0; i < parameters.length; i += 2) {
			String name = parameters[i].toString();

			String value = parameters[i + 1].toString();
			query.add(name, value);
		}
		return this;
	}

	/**
	 * Adds all parameters from the provided map.
	 */
	public HttpRequest query(final Map queryMap) {
		for (Map.Entry entry : queryMap.entrySet()) {
			query.add(entry.getKey(), entry.getValue());
		}
		return this;
	}

	/**
	 * Returns backend map of query parameters.
	 */
	public HttpMultiMap query() {
		return query;
	}

	/**
	 * Clears all query parameters.
	 */
	public HttpRequest clearQueries() {
		query.clear();
		return this;
	}

	/**
	 * Removes query parameters for given name.
	 */
	public HttpRequest queryRemove(final String name) {
		query.remove(name);
		return this;
	}

	// ---------------------------------------------------------------- queryString

	/**
	 * @see #queryString(String, boolean)
	 */
	public HttpRequest queryString(final String queryString) {
		return queryString(queryString, true);
	}

	/**
	 * Sets query from provided query string. Previous query values
	 * are discarded.
	 */
	public HttpRequest queryString(final String queryString, final boolean decode) {
		this.query = HttpUtil.parseQuery(queryString, decode);
		return this;
	}

	/**
	 * Generates query string. All values are URL encoded.
	 */
	public String queryString() {
		if (query == null) {
			return StringPool.EMPTY;
		}
		return HttpUtil.buildQuery(query, queryEncoding);
	}

	// ---------------------------------------------------------------- query encoding

	protected String queryEncoding = Defaults.queryEncoding;

	/**
	 * Defines encoding for query parameters.
	 */
	public HttpRequest queryEncoding(final String encoding) {
		this.queryEncoding = encoding;
		return this;
	}

	// ---------------------------------------------------------------- full path

	/**
	 * Returns full URL path.
	 * Simply concatenates {@link #protocol(String) protocol}, {@link #host(String) host},
	 * {@link #port(int) port}, {@link #path(String) path} and {@link #queryString(String) query string}.
	 */
	public String url() {
		StringBuilder url = new StringBuilder();

		url.append(hostUrl());

		if (path != null) {
			url.append(path);
		}

		String queryString = queryString();

		if (StringUtil.isNotBlank(queryString)) {
			url.append('?');
			url.append(queryString);
		}

		return url.toString();
	}

	/**
	 * Returns just host url, without path and query.
	 */
	public String hostUrl() {
		StringBand url = new StringBand(8);

		if (protocol != null) {
			url.append(protocol);
			url.append("://");
		}

		if (host != null) {
			url.append(host);
		}

		if (port != Defaults.DEFAULT_PORT) {
			url.append(':');
			url.append(port);
		}

		return url.toString();
	}

	// ---------------------------------------------------------------- auth

	/**
	 * Enables basic authentication by adding required header.
	 */
	public HttpRequest basicAuthentication(final String username, final String password) {
		if (username != null && password != null) {
			String data = username.concat(StringPool.COLON).concat(password);

			String base64 = Base64.encodeToString(data);

			headerOverwrite(HEADER_AUTHORIZATION, "Basic " + base64);
		}

		return this;
	}

	/**
	 * Enables token-based authentication.
	 */
	public HttpRequest tokenAuthentication(final String token) {
		if (token != null) {
			headerOverwrite(HEADER_AUTHORIZATION, "Bearer " + token);
		}
		return this;
	}


	// ---------------------------------------------------------------- https

	private boolean trustAllCertificates;
	private boolean verifyHttpsHost = true;

	/**
	 * Trusts all certificates, use with caution.
	 */
	public HttpRequest trustAllCerts(final boolean trust) {
		trustAllCertificates = trust;
		return this;
	}

	/**
	 * Returns a flag if to trusts all certificates.
	 */
	public boolean trustAllCertificates() {
		return trustAllCertificates;
	}

	/**
	 * Verifies HTTPS hosts.
	 */
	public HttpRequest verifyHttpsHost(final boolean verifyHttpsHost) {
		this.verifyHttpsHost = verifyHttpsHost;
		return this;
	}

	/**
	 * Returns a flag if to verify https hosts.
	 */
	public boolean verifyHttpsHost() {
		return verifyHttpsHost;
	}

	// ---------------------------------------------------------------- misc

	/**
	 * Sets 'Host' header from current host and port.
	 */
	public HttpRequest setHostHeader() {
		String hostPort = this.host;

		if (port != Defaults.DEFAULT_PORT) {
			hostPort += StringPool.COLON + port;
		}

		headerOverwrite(HEADER_HOST, hostPort);
		return this;
	}

	// ---------------------------------------------------------------- monitor

	/**
	 * Registers {@link jodd.http.HttpProgressListener listener} that will
	 * monitor upload progress. Be aware that the whole size of the
	 * request is being monitored, not only the files content.
	 */
	public HttpRequest monitor(final HttpProgressListener httpProgressListener) {
		this.httpProgressListener = httpProgressListener;
		return this;
	}

	// ---------------------------------------------------------------- connection properties

	protected int timeout = -1;
	protected int connectTimeout = -1;
	protected boolean followRedirects = false;
	protected int maxRedirects = 50;

	/**
	 * Defines the socket timeout (SO_TIMEOUT) in milliseconds, which is the timeout for waiting for data or,
	 * put differently, a maximum period inactivity between two consecutive data packets).
	 * After establishing the connection, the client socket waits for response after sending
	 * the request. This is the elapsed time since the client has sent request to the
	 * server before server responds. Please note that this is not same as HTTP Error 408 which
	 * the server sends to the client. In other words its maximum period inactivity between
	 * two consecutive data packets arriving at client side after connection is established.
	 * A timeout value of zero is interpreted as an infinite timeout.
	 * @see jodd.http.HttpConnection#setTimeout(int)
	 */
	public HttpRequest timeout(final int milliseconds) {
		this.timeout = milliseconds;
		return this;
	}

	/**
	 * Returns read timeout (SO_TIMEOUT) in milliseconds. Negative value
	 * means that default value is used.
	 * @see #timeout(int)
	 */
	public int timeout() {
		return timeout;
	}

	/**
	 * Defines the socket timeout (SO_TIMEOUT) in milliseconds, which is the timeout
	 * for waiting for data or, put differently, a maximum period inactivity between
	 * two consecutive data packets). A timeout value of zero is interpreted as
	 * an infinite timeout.
	 */
	public HttpRequest connectionTimeout(final int milliseconds) {
		this.connectTimeout = milliseconds;
		return this;
	}

	/**
	 * Returns socket connection timeout. Negative value means that default
	 * value is used.
	 * @see #connectionTimeout(int)
	 */
	public int connectionTimeout() {
		return connectTimeout;
	}

	/**
	 * Defines if redirects responses should be followed. NOTE: when redirection is enabled,
	 * the original URL will NOT be preserved in the request!
	 */
	public HttpRequest followRedirects(final boolean followRedirects) {
		this.followRedirects = followRedirects;
		return this;
	}

	/**
	 * Returns {@code true} if redirects are followed.
	 */
	public boolean isFollowRedirects() {
		return this.followRedirects;
	}

	/**
	 * Sets the max number of redirects, used when {@link #followRedirects} is enabled.
	 */
	public HttpRequest maxRedirects(final int maxRedirects) {
		this.maxRedirects = maxRedirects;
		return this;
	}

	/**
	 * Returns max number of redirects, used when {@link #followRedirects} is enabled.
	 */
	public int maxRedirects() {
		return this.maxRedirects;
	}


	// ---------------------------------------------------------------- send

	protected HttpConnection httpConnection;
	protected HttpConnectionProvider httpConnectionProvider;

	/**
	 * Uses custom connection provider when {@link #open() opening} the
	 * connection.
	 */
	public HttpRequest withConnectionProvider(final HttpConnectionProvider httpConnectionProvider) {
		this.httpConnectionProvider = httpConnectionProvider;
		return this;
	}

	/**
	 * Returns http connection provider that was used for creating
	 * current http connection. If null, default
	 * connection provider will be used.
	 */
	public HttpConnectionProvider connectionProvider() {
		return httpConnectionProvider;
	}

	/**
	 * Returns {@link HttpConnection} that is going to be
	 * used for sending this request. Value is available
	 * ONLY after calling {@link #open()} and before {@link #send()}.
	 */
	public HttpConnection connection() {
		return httpConnection;
	}

	/**
	 * Opens a new {@link HttpConnection connection} using either
	 * provided or {@link HttpConnectionProvider default} connection
	 * provider.
	 */
	public HttpRequest open() {
		if (httpConnectionProvider == null) {
			return open(HttpConnectionProvider.get());
		}

		return open(httpConnectionProvider);
	}

	/**
	 * Opens a new {@link jodd.http.HttpConnection connection}
	 * using given {@link jodd.http.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 (IOException ioex) {
			throw new HttpException("Can't connect to: " + url(), ioex);
		}

		return this;
	}

	/**
	 * Assignees provided {@link jodd.http.HttpConnection} for communication.
	 * It does not actually opens it until the {@link #send() sending}.
	 */
	public HttpRequest open(final HttpConnection httpConnection) {
		if (this.httpConnection != null) {
			throw new HttpException("Connection already opened");
		}
		this.httpConnection = httpConnection;
		this.httpConnectionProvider = null;
		return this;
	}

	/**
	 * Continues using the same keep-alive connection.
	 * Don't use any variant of open() when
	 * continuing the communication!
	 * First it checks if "Connection" header exist in the response
	 * and if it is equal to "Keep-Alive" value. Then it
	 * checks the "Keep-Alive" headers "max" parameter.
	 * If its value is positive, then the existing {@link jodd.http.HttpConnection}
	 * from the request will be reused. If max value is 1,
	 * connection will be sent with "Connection: Close" header, indicating
	 * its the last request. When new connection is created, the
	 * same {@link jodd.http.HttpConnectionProvider} that was used for
	 * creating initial connection is used for opening the new connection.
	 *
	 * @param doContinue set it to false to indicate the last connection
	 */
	public HttpRequest keepAlive(final HttpResponse httpResponse, final boolean doContinue) {
		boolean keepAlive = httpResponse.isConnectionPersistent();
		if (keepAlive) {
			HttpConnection previousConnection = httpResponse.getHttpRequest().httpConnection;

			if (previousConnection != null) {
				// keep using the connection!
				this.httpConnection = previousConnection;
				this.httpConnectionProvider = httpResponse.getHttpRequest().connectionProvider();
			}

			//keepAlive = true; (already set)
		} else {
			// close previous connection
			httpResponse.close();

			// force keep-alive on new request
			keepAlive = true;
		}

		// if we don't want to continue with this persistent session, mark this connection as closed
		if (!doContinue) {
			keepAlive = false;
		}

		connectionKeepAlive(keepAlive);

		// if connection is not opened, open it using previous connection provider
		if (httpConnection == null) {
			open(httpResponse.getHttpRequest().connectionProvider());
		}
		return this;
	}

	/**
	 * {@link #open() Opens connection} if not already open, sends request,
	 * reads response and closes the request. If keep-alive mode is enabled
	 * connection will not be closed.
	 */
	public HttpResponse send() {
		if (!followRedirects) {
			return _send();
		}

		int redirects = this.maxRedirects;

		while (redirects > 0) {
			redirects--;

			final HttpResponse httpResponse = _send();

			final int statusCode = httpResponse.statusCode();

			if (HttpStatus.isRedirect(statusCode)) {
				_reset();
				set(httpResponse.location());
				continue;
			}

			return httpResponse;
		}

		throw new HttpException("Max number of redirects exceeded: " + this.maxRedirects);
	}

	/**
	 * Resets the request by resetting all additional values
	 * added during the sending.
	 */
	private void _reset() {
		headers.remove(HEADER_HOST);
	}

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

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

			sendTo(outputStream);

			InputStream inputStream = httpConnection.getInputStream();

			httpResponse = HttpResponse.readFrom(inputStream);

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

		boolean keepAlive = httpResponse.isConnectionPersistent();

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

		return httpResponse;
	}

	// ---------------------------------------------------------------- buffer

	/**
	 * Prepares the request buffer.
	 */
	@Override
	protected Buffer buffer(final boolean fullRequest) {
		// INITIALIZATION

		// host port

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

		// form

		Buffer formBuffer = formBuffer();

		// query string

		String queryString = queryString();

		// user-agent

		if (header("User-Agent") == null) {
			header("User-Agent", Defaults.userAgent);
		}

		// POST method requires Content-Type to be set

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


		// BUILD OUT

		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);

		populateHeaderAndBody(request, formBuffer, fullRequest);

		return request;
	}

	// ---------------------------------------------------------------- parse

	/**
	 * Parses input stream and creates new HttpRequest object.
	 * Assumes input stream is in ISO_8859_1 encoding.
	 */
	public static HttpRequest readFrom(final InputStream in) {
		return readFrom(in, StringPool.ISO_8859_1);
	}

	public static HttpRequest readFrom(final InputStream in, final String encoding) {
		BufferedReader reader;
		try {
			reader = new BufferedReader(new InputStreamReader(in, encoding));
		} catch (UnsupportedEncodingException uneex) {
			return null;
		}

		final HttpRequest httpRequest = new HttpRequest();
		httpRequest.headers.clear();

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

		if (!StringUtil.isBlank(line)) {
			String[] s = StringUtil.splitc(line, ' ');

			httpRequest.method(s[0]);
			httpRequest.path(s[1]);
			httpRequest.httpVersion(s[2]);

			httpRequest.readHeaders(reader);
			httpRequest.readBody(reader);
		}

		return httpRequest;
	}


	// ---------------------------------------------------------------- shortcuts

	/**
	 * Specifies JSON content type.
	 */
	public HttpRequest contentTypeJson() {
		return contentType(MimeTypes.MIME_APPLICATION_JSON);
	}

	/**
	 * Accepts JSON content type.
	 */
	public HttpRequest acceptJson() {
		return accept(MimeTypes.MIME_APPLICATION_JSON);
	}


	// ---------------------------------------------------------------- functional/async

	/**
	 * Sends http request asynchronously using common fork-join pool.
	 * Note that this is not the right non-blocking call (not a NIO), it is just
	 * a regular call that is operated in a separate thread.
	 */
	public CompletableFuture sendAsync() {
		return CompletableFuture.supplyAsync(this::send);
	}

	/**
	 * Syntax sugar.
	 */
	public  R sendAndReceive(final Function responseHandler) {
		return responseHandler.apply(send());
	}

	/**
	 * Syntax sugar.
	 */
	public void sendAndReceive(final Consumer responseHandler) {
		responseHandler.accept(send());
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy