jodd.http.HttpBase Maven / Gradle / Ivy
// 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.http.up.ByteArrayUploadable;
import jodd.http.up.FileUploadable;
import jodd.http.up.Uploadable;
import jodd.io.FastCharArrayWriter;
import jodd.io.FileNameUtil;
import jodd.io.StreamUtil;
import jodd.io.upload.FileUpload;
import jodd.io.upload.MultipartStreamParser;
import jodd.net.MimeTypes;
import jodd.time.TimeUtil;
import jodd.util.RandomString;
import jodd.util.StringPool;
import jodd.util.StringUtil;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static jodd.util.StringPool.CRLF;
/**
* Base class for {@link HttpRequest} and {@link HttpResponse}.
*/
public abstract class HttpBase {
public static class Defaults {
public static final int DEFAULT_PORT = -1;
/**
* Default HTTP query parameters encoding (UTF-8).
*/
public static String queryEncoding = StringPool.UTF_8;
/**
* Default form encoding (UTF-8).
*/
public static String formEncoding = StringPool.UTF_8;
/**
* Default body media type.
*/
public static String bodyMediaType = MimeTypes.MIME_TEXT_HTML;
/**
* Default body encoding (UTF-8).
*/
public static String bodyEncoding = StringPool.UTF_8;
/**
* Default user agent value.
*/
public static String userAgent = "Jodd HTTP";
/**
* Flag that controls if headers should be rewritten and capitalized in PascalCase.
* When disabled, header keys are used as they are passed.
* When flag is enabled, header keys will be capitalized.
*/
public static boolean capitalizeHeaderKeys = true;
}
public static final String HEADER_ACCEPT = "Accept";
public static final String HEADER_AUTHORIZATION = "Authorization";
public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
public static final String HEADER_CONTENT_TYPE = "Content-Type";
public static final String HEADER_CONTENT_LENGTH = "Content-Length";
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
public static final String HEADER_HOST = "Host";
public static final String HEADER_ETAG = "ETag";
public static final String HEADER_CONNECTION = "Connection";
public static final String HEADER_KEEP_ALIVE = "Keep-Alive";
public static final String HEADER_CLOSE = "Close";
public static final String HTTP_1_0 = "HTTP/1.0";
public static final String HTTP_1_1 = "HTTP/1.1";
protected String httpVersion = HTTP_1_1;
protected boolean capitalizeHeaderKeys = Defaults.capitalizeHeaderKeys;
protected final HeadersMultiMap headers = new HeadersMultiMap();
protected HttpMultiMap> form; // holds form data (when used)
protected String body; // holds raw body string (always)
@SuppressWarnings("unchecked")
protected T _this() {
return (T) this;
}
// ---------------------------------------------------------------- properties
/**
* Returns HTTP version string. By default it's "HTTP/1.1".
*/
public String httpVersion() {
return httpVersion;
}
/**
* Sets the HTTP version string. Must be formed like "HTTP/1.1".
*/
public T httpVersion(final String httpVersion) {
this.httpVersion = httpVersion;
return _this();
}
/**
* Returns whether header keys should be strict or not, when they are
* modified by changing them to PascalCase.
* @see Defaults#capitalizeHeaderKeys
*/
public boolean capitalizeHeaderKeys() {
return capitalizeHeaderKeys;
}
/**
* Sets headers behavior.
* @see Defaults#capitalizeHeaderKeys
*/
public T capitalizeHeaderKeys(final boolean capitalizeHeaderKeys) {
this.capitalizeHeaderKeys = capitalizeHeaderKeys;
return _this();
}
// ---------------------------------------------------------------- headers
/**
* Returns value of header parameter.
* If multiple headers with the same names exist,
* the first value will be returned. Returns null
* if header doesn't exist.
*/
public String header(final String name) {
return headers.getHeader(name);
}
/**
* Returns all values for given header name.
*/
public List headers(final String name) {
return headers.getAll(name);
}
/**
* Removes all header parameters for given name.
*/
public void headerRemove(final String name) {
headers.remove(name.trim());
}
/**
* Adds header parameter. If a header with the same name exist,
* it will not be overwritten, but the new header with the same
* name is going to be added.
* The order of header parameters is preserved.
* Also detects 'Content-Type' header and extracts
* {@link #mediaType() media type} and {@link #charset() charset}
* values.
*/
public T header(final String name, final String value) {
return _header(name, value, false);
}
/**
* Adds many header parameters at once.
* @see #header(String, String)
*/
public T header(final Map headerMap) {
for (Map.Entry entry : headerMap.entrySet()) {
header(entry.getKey(), entry.getValue());
}
return _this();
}
/**
* Sets the header by overwriting it.
* @see #header(String, String)
*/
public T headerOverwrite(final String name, String value) {
return _header(name, value, true);
}
/**
* Adds or sets header parameter.
* @see #header(String, String)
*/
protected T _header(final String name, String value, final boolean overwrite) {
String key = name.trim();
if (key.equalsIgnoreCase(HEADER_CONTENT_TYPE)) {
value = value.trim();
mediaType = HttpUtil.extractMediaType(value);
charset = HttpUtil.extractContentTypeCharset(value);
}
_headerRaw(name, value, overwrite);
return _this();
}
/**
* Internal direct header setting.
*/
protected void _headerRaw(String name, String value, final boolean overwrite) {
name = name.trim();
value = value.trim();
if (overwrite) {
headers.setHeader(name, value);
} else {
headers.addHeader(name, value);
}
}
/**
* Adds int
value as header parameter,
* @see #header(String, String)
*/
public T header(final String name, final int value) {
_headerRaw(name, String.valueOf(value), false);
return _this();
}
/**
* Adds date value as header parameter.
* @see #header(String, String)
*/
public T header(final String name, final long millis) {
_headerRaw(name, TimeUtil.formatHttpDate(millis), false);
return _this();
}
/**
* Returns collection of all header names. Depends on
* {@link #capitalizeHeaderKeys()} flag.
*/
public Collection headerNames() {
return headers.names();
}
/**
* Returns Bearer token or {@code null} if not set.
*/
public String tokenAuthentication() {
final String value = headers.get(HEADER_AUTHORIZATION);
if (value == null) {
return null;
}
final int ndx = value.indexOf("Bearer ");
if (ndx == -1) {
return null;
}
return value.substring(ndx + 7).trim();
}
// ---------------------------------------------------------------- content type
protected String charset;
/**
* Returns charset, as defined by 'Content-Type' header.
* If not set, returns null
- indicating
* the default charset (ISO-8859-1).
*/
public String charset() {
return charset;
}
/**
* Defines just content type charset. Setting this value to
* null
will remove the charset information from
* the header.
*/
public T charset(final String charset) {
this.charset = null;
contentType(null, charset);
return _this();
}
protected String mediaType;
/**
* Returns media type, as defined by 'Content-Type' header.
* If not set, returns null
- indicating
* the default media type, depending on request/response.
*/
public String mediaType() {
return mediaType;
}
/**
* Defines just content media type.
* Setting this value to null
will
* not have any effects.
*/
public T mediaType(final String mediaType) {
contentType(mediaType, null);
return _this();
}
/**
* Returns full "Content-Type" header.
* It consists of {@link #mediaType() media type}
* and {@link #charset() charset}.
*/
public String contentType() {
return header(HEADER_CONTENT_TYPE);
}
/**
* Sets full "Content-Type" header. Both {@link #mediaType() media type}
* and {@link #charset() charset} are overridden.
*/
public T contentType(final String contentType) {
headerOverwrite(HEADER_CONTENT_TYPE, contentType);
return _this();
}
/**
* Sets "Content-Type" header by defining media-type and/or charset parameter.
* This method may be used to update media-type and/or charset by passing
* non-null
value for changes.
*
* Important: if Content-Type header has some other parameters, they will be removed!
*/
public T contentType(String mediaType, String charset) {
if (mediaType == null) {
mediaType = this.mediaType;
} else {
this.mediaType = mediaType;
}
if (charset == null) {
charset = this.charset;
} else {
this.charset = charset;
}
String contentType = mediaType;
if (charset != null) {
contentType += ";charset=" + charset;
}
_headerRaw(HEADER_CONTENT_TYPE, contentType, true);
return _this();
}
// ---------------------------------------------------------------- keep-alive
/**
* Defines "Connection" header as "Keep-Alive" or "Close".
* Existing value is overwritten.
*/
public T connectionKeepAlive(final boolean keepAlive) {
if (keepAlive) {
headerOverwrite(HEADER_CONNECTION, HEADER_KEEP_ALIVE);
} else {
headerOverwrite(HEADER_CONNECTION, HEADER_CLOSE);
}
return _this();
}
/**
* Returns true
if connection is persistent.
* If "Connection" header does not exist, returns true
* for HTTP 1.1 and false
for HTTP 1.0. If
* "Connection" header exist, checks if it is equal to "Close".
*
* In HTTP 1.1, all connections are considered persistent unless declared otherwise.
* Under HTTP 1.0, there is no official specification for how keepalive operates.
*/
public boolean isConnectionPersistent() {
String connection = header(HEADER_CONNECTION);
if (connection == null) {
return !httpVersion.equalsIgnoreCase(HTTP_1_0);
}
return !connection.equalsIgnoreCase(HEADER_CLOSE);
}
// ---------------------------------------------------------------- common headers
/**
* Returns full "Content-Length" header or
* null
if not set. Returned value is raw and unchecked, exactly the same
* as it was specified or received. It may be even invalid.
*/
public String contentLength() {
return header(HEADER_CONTENT_LENGTH);
}
/**
* Sets the full "Content-Length" header.
*/
public T contentLength(final int value) {
_headerRaw(HEADER_CONTENT_LENGTH, String.valueOf(value), true);
return _this();
}
/**
* Returns "Content-Encoding" header.
*/
public String contentEncoding() {
return header(HEADER_CONTENT_ENCODING);
}
/**
* Returns "Accept" header.
*/
public String accept() {
return header(HEADER_ACCEPT);
}
/**
* Sets "Accept" header.
*/
public T accept(final String encodings) {
headerOverwrite(HEADER_ACCEPT, encodings);
return _this();
}
/**
* Returns "Accept-Encoding" header.
*/
public String acceptEncoding() {
return header(HEADER_ACCEPT_ENCODING);
}
/**
* Sets "Accept-Encoding" header.
*/
public T acceptEncoding(final String encodings) {
headerOverwrite(HEADER_ACCEPT_ENCODING, encodings);
return _this();
}
// ---------------------------------------------------------------- form
/**
* Initializes form.
*/
protected void initForm() {
if (form == null) {
form = HttpMultiMap.newCaseInsensitiveMap();
}
}
/**
* Wraps non-Strings form values with {@link jodd.http.up.Uploadable uploadable content}.
* Detects invalid types and throws an exception. So all uploadable values
* are of the same type.
*/
protected Object wrapFormValue(final Object value) {
if (value == null) {
return null;
}
if (value instanceof CharSequence) {
return value.toString();
}
if (value instanceof Number) {
return value.toString();
}
if (value instanceof Boolean) {
return value.toString();
}
if (value instanceof File) {
return new FileUploadable((File) value);
}
if (value instanceof byte[]) {
return new ByteArrayUploadable((byte[]) value, null);
}
if (value instanceof Uploadable) {
return value;
}
throw new HttpException("Unsupported value type: " + value.getClass().getName());
}
/**
* Adds the form parameter. Existing parameter will not be overwritten.
*/
public T form(final String name, Object value) {
initForm();
value = wrapFormValue(value);
((HttpMultiMap