HttpReq.java
package space.sunqian.common.net.http;
import space.sunqian.annotations.Nonnull;
import space.sunqian.annotations.Nullable;
import space.sunqian.annotations.RetainedParam;
import space.sunqian.common.Check;
import space.sunqian.common.Fs;
import space.sunqian.common.base.chars.CharsKit;
import space.sunqian.common.io.BufferKit;
import space.sunqian.common.io.IOKit;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Http request info, can be built by {@link #newBuilder()}.
*
* @author sunqian
*/
public interface HttpReq {
/**
* The default request timeout: 30 seconds.
*/
@Nonnull
Duration DEFAULT_REQUEST_TIMEOUT = Duration.ofSeconds(30);
/**
* Returns a new builder for {@link HttpReq}.
*
* @return a new builder
*/
static @Nonnull Builder newBuilder() {
return new Builder();
}
/**
* Returns the url of the request.
*
* @return the url of the request
*/
@Nonnull
URL url();
/**
* Returns the method of the request.
*
* @return the method of the request
*/
@Nonnull
String method();
/**
* Returns the headers of the request.
*
* @return the headers of the request
*/
@Nonnull
Map<@Nonnull String, @Nonnull List<@Nonnull String>> headers();
/**
* Returns the body of the request, or {@code null} if no body data.
*
* @return the body of the request, or {@code null} if no body data
*/
@Nullable
Body body();
/**
* Returns the timeout of the request. The default is {@link #DEFAULT_REQUEST_TIMEOUT}.
*
* @return the timeout of the request
*/
@Nonnull
Duration timeout();
/**
* Represents the body of the request.
*/
interface Body {
/**
* Returns the type of the body.
*
* @return the type of the body
*/
@Nonnull
Type type();
/**
* Returns an {@link InputStream} to read the data of the body. If the data is originally of input stream type,
* return the data itself.
*
* @return an {@link InputStream} to read the data of the body
*/
@Nonnull
InputStream toInputStream();
/**
* Returns a byte array represents the data of the body. If the data is originally of byte array type, return
* the data itself.
*
* @return a byte array represents the data of the body
*/
byte @Nonnull [] toByteArray();
/**
* Returns a byte buffer represents the data of the body. If the data is originally of byte buffer type, return
* the data itself.
*
* @return a byte buffer represents the data of the body
*/
@Nonnull
ByteBuffer toByteBuffer();
/**
* Returns a string represents the data of the body using {@link CharsKit#defaultCharset()}. If the data is
* originally of string type, return the data itself.
*
* @return a string represents the data of the body using {@link CharsKit#defaultCharset()}
*/
@Nonnull
String toText();
/**
* Type of request body.
*/
enum Type {
/**
* Input stream.
*/
INPUT_STREAM,
/**
* Byte Array.
*/
BYTE_ARRAY,
/**
* Byte Buffer.
*/
BYTE_BUFFER,
/**
* Text.
*/
TEXT
}
}
/**
* Builder for {@link HttpReq}.
*/
class Builder {
private URL url;
private String method = "GET";
private Map<String, List<String>> headers;
private @Nullable Body body;
private @Nonnull Duration timeout = DEFAULT_REQUEST_TIMEOUT;
/**
* Sets the url of the request.
*
* @param url the url of the request
* @return this builder
*/
public @Nonnull Builder url(@Nonnull URL url) {
this.url = url;
return this;
}
/**
* Sets the url of the request.
*
* @param url the url of the request
* @return this builder
* @throws HttpNetException if the url is invalid
*/
public @Nonnull Builder url(@Nonnull String url) throws HttpNetException {
return url(Fs.uncheck(() -> new URL(url), HttpNetException::new));
}
/**
* Sets the method of the request.
*
* @param method the method of the request
* @return this builder
*/
public @Nonnull Builder method(@Nonnull String method) {
this.method = method;
return this;
}
/**
* Sets the headers of the request.
*
* @param headers the headers of the request
* @return this builder
*/
public @Nonnull Builder headers(
@Nonnull @RetainedParam Map<@Nonnull String, @Nonnull List<@Nonnull String>> headers
) {
this.headers = headers;
return this;
}
/**
* Adds a header of the request on the current builder.
*
* @param key the key of the header
* @param value the value of the header
* @return this builder
*/
public @Nonnull Builder header(@Nonnull String key, @Nonnull String value) {
Map<String, List<String>> headers = this.headers;
if (headers == null) {
headers = new LinkedHashMap<>();
this.headers = headers;
}
headers.compute(key, (k, l) -> {
if (l == null) {
List<String> list = new ArrayList<>(1);
list.add(value);
return list;
} else {
l.add(value);
return l;
}
});
return this;
}
/**
* Sets the body of the request.
*
* @param body the body from the specified input stream
* @return this builder
*/
public @Nonnull Builder body(@Nonnull InputStream body) {
this.body = new Body() {
@Override
public @Nonnull Type type() {
return Type.INPUT_STREAM;
}
@Override
public @Nonnull InputStream toInputStream() {
return body;
}
@Override
public byte @Nonnull [] toByteArray() {
return Fs.asNonnull(IOKit.read(body));
}
@Override
public @Nonnull ByteBuffer toByteBuffer() {
return ByteBuffer.wrap(toByteArray());
}
@Override
public @Nonnull String toText() {
return Fs.asNonnull(IOKit.string(body));
}
};
return this;
}
/**
* Sets the body of the request. The body uses {@link CharsKit#defaultCharset()}.
*
* @param body the body from the specified string
* @return this builder
*/
public @Nonnull Builder body(@Nonnull String body) {
this.body = new Body() {
@Override
public @Nonnull Type type() {
return Type.TEXT;
}
@Override
public @Nonnull InputStream toInputStream() {
return new ByteArrayInputStream(body.getBytes(CharsKit.defaultCharset()));
}
@Override
public byte @Nonnull [] toByteArray() {
return body.getBytes(CharsKit.defaultCharset());
}
@Override
public @Nonnull ByteBuffer toByteBuffer() {
return ByteBuffer.wrap(toByteArray());
}
@Override
public @Nonnull String toText() {
return body;
}
};
return this;
}
/**
* Sets the body of the request.
*
* @param body the body from the specified byte array
* @return this builder
*/
public @Nonnull Builder body(byte @Nonnull [] body) {
this.body = new Body() {
@Override
public @Nonnull Type type() {
return Type.BYTE_ARRAY;
}
@Override
public @Nonnull InputStream toInputStream() {
return new ByteArrayInputStream(body);
}
@Override
public byte @Nonnull [] toByteArray() {
return body;
}
@Override
public @Nonnull ByteBuffer toByteBuffer() {
return ByteBuffer.wrap(toByteArray());
}
@Override
public @Nonnull String toText() {
return new String(body, CharsKit.defaultCharset());
}
};
return this;
}
/**
* Sets the body of the request.
*
* @param body the body from the specified byte buffer
* @return this builder
*/
public @Nonnull Builder body(@Nonnull ByteBuffer body) {
this.body = new Body() {
@Override
public @Nonnull Type type() {
return Type.BYTE_BUFFER;
}
@Override
public @Nonnull InputStream toInputStream() {
return IOKit.newInputStream(body);
}
@Override
public byte @Nonnull [] toByteArray() {
return Fs.asNonnull(BufferKit.read(body));
}
@Override
public @Nonnull ByteBuffer toByteBuffer() {
return body;
}
@Override
public @Nonnull String toText() {
return new String(toByteArray(), CharsKit.defaultCharset());
}
};
return this;
}
/**
* Sets the timeout of the request.
*
* @param timeout the timeout of the request
* @return this builder
*/
public @Nonnull Builder timeout(@Nonnull Duration timeout) {
this.timeout = timeout;
return this;
}
/**
* Builds and returns a {@link HttpReq} with the configurations.
*
* @return a {@link HttpReq} with the configurations
* @throws IllegalArgumentException if there exists invalid arguments
*/
public @Nonnull HttpReq build() throws IllegalArgumentException {
Check.checkArgument(url != null, "The url can not be null.");
// CheckKit.checkArgument(method != null, "The method can not be null.");
return new HttpReqImpl(
url,
method,
Fs.nonnull(headers, Collections.emptyMap()),
body,
timeout
);
}
private static final class HttpReqImpl implements HttpReq {
private final @Nonnull URL url;
private final @Nonnull String method;
private final @Nonnull Map<String, List<String>> headers;
private final @Nullable Body body;
private final @Nonnull Duration timeout;
private HttpReqImpl(
@Nonnull URL url,
@Nonnull String method,
@Nonnull Map<String, List<String>> headers,
@Nullable Body body,
@Nonnull Duration timeout
) {
this.url = url;
this.method = method;
this.headers = headers;
this.body = body;
this.timeout = timeout;
}
@Override
public @Nonnull URL url() {
return url;
}
@Override
public @Nonnull String method() {
return method;
}
@Override
public @Nonnull Map<String, List<String>> headers() {
return headers;
}
@Override
public @Nullable Body body() {
return body;
}
@Override
public @Nonnull Duration timeout() {
return timeout;
}
}
}
}