
com.hazelcast.internal.ascii.rest.HttpCommand Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hazelcast.internal.ascii.rest;
import com.hazelcast.internal.ascii.AbstractTextCommand;
import com.hazelcast.internal.ascii.TextCommandConstants;
import com.hazelcast.internal.ascii.TextCommandService;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import javax.annotation.Nullable;
import java.net.HttpURLConnection;
import java.nio.ByteBuffer;
import java.util.Map;
import static com.hazelcast.internal.ascii.rest.HttpStatusCode.SC_200;
import static com.hazelcast.internal.ascii.rest.HttpStatusCode.SC_204;
import static com.hazelcast.internal.ascii.rest.HttpStatusCode.SC_400;
import static com.hazelcast.internal.ascii.rest.HttpStatusCode.SC_403;
import static com.hazelcast.internal.ascii.rest.HttpStatusCode.SC_404;
import static com.hazelcast.internal.ascii.rest.HttpStatusCode.SC_500;
import static com.hazelcast.internal.ascii.rest.HttpStatusCode.SC_503;
import static com.hazelcast.internal.nio.IOUtil.copyFromHeapBuffer;
import static com.hazelcast.internal.util.StringUtil.stringToBytes;
@SuppressFBWarnings({"EI_EXPOSE_REP", "MS_MUTABLE_ARRAY", "MS_PKGPROTECT"})
public abstract class HttpCommand extends AbstractTextCommand {
public static final byte[] CONTENT_TYPE_PLAIN_TEXT = stringToBytes("text/plain");
public static final byte[] CONTENT_TYPE_JSON = stringToBytes("application/json");
public static final byte[] CONTENT_TYPE_BINARY = stringToBytes("application/binary");
static final String HEADER_CONTENT_TYPE = "content-type: ";
static final String HEADER_CONTENT_LENGTH = "content-length: ";
static final String HEADER_CHUNKED = "transfer-encoding: chunked";
static final String HEADER_EXPECT_100 = "expect: 100";
private static final String HEADER_CUSTOM_PREFIX = "Hazelcast-";
private static final byte[] CONTENT_TYPE = stringToBytes("Content-Type: ");
private static final byte[] CONTENT_LENGTH = stringToBytes("Content-Length: ");
protected final String uri;
protected ByteBuffer response;
protected boolean nextLine;
private final RestCallExecution executionDetails = new RestCallExecution();
public HttpCommand(TextCommandConstants.TextCommandType type, String uri) {
super(type);
this.uri = uri;
// the command line was parsed already, let's start with clear next line
this.nextLine = true;
executionDetails.setRequestPath(uri);
}
@Override
public boolean shouldReply() {
return true;
}
public String getURI() {
return uri;
}
/**
* Prepares a {@link HttpURLConnection#HTTP_NO_CONTENT} response.
*/
public void send204() {
setResponse(SC_204, null, null);
}
/**
* Prepares a {@link HttpURLConnection#HTTP_BAD_REQUEST} response.
*/
public void send400() {
setResponse(SC_400, null, null);
}
/**
* Prepares a {@link HttpURLConnection#HTTP_FORBIDDEN} response.
*/
public void send403() {
setResponse(SC_403, null, null);
}
/**
* Prepares a {@link HttpURLConnection#HTTP_NOT_FOUND} response.
*/
public void send404() {
setResponse(SC_404, null, null);
}
/**
* Prepares a {@link HttpURLConnection#HTTP_INTERNAL_ERROR} response.
*/
public void send500() {
setResponse(SC_500, null, null);
}
/**
* Prepares a {@link HttpURLConnection#HTTP_UNAVAILABLE} response.
*/
public void send503() {
setResponse(SC_503, null, null);
}
/**
* Prepares an empty {@link HttpURLConnection#HTTP_OK} response.
*/
public void send200() {
setResponse(SC_200, null, null);
}
/**
* Prepares an HTTP response with no content and the provided status line and
* response headers.
*
* @param statusCode the HTTP response status code
* @param headers the map of response headers
*/
public void setResponse(HttpStatusCode statusCode,
@Nullable Map headers) {
byte[] statusLine = statusCode.statusLine;
int size = statusLine.length;
byte[] len = stringToBytes(String.valueOf(0));
size += CONTENT_LENGTH.length;
size += len.length;
size += TextCommandConstants.RETURN.length;
if (headers != null) {
for (Map.Entry entry : headers.entrySet()) {
size += stringToBytes(HEADER_CUSTOM_PREFIX + entry.getKey() + ": ").length;
size += stringToBytes(entry.getValue().toString()).length;
size += TextCommandConstants.RETURN.length;
}
}
size += TextCommandConstants.RETURN.length;
this.response = ByteBuffer.allocate(size);
response.put(statusLine);
response.put(CONTENT_LENGTH);
response.put(len);
response.put(TextCommandConstants.RETURN);
if (headers != null) {
for (Map.Entry entry : headers.entrySet()) {
response.put(stringToBytes(HEADER_CUSTOM_PREFIX + entry.getKey() + ": "));
response.put(stringToBytes(entry.getValue().toString()));
response.put(TextCommandConstants.RETURN);
}
}
response.put(TextCommandConstants.RETURN);
response.flip();
setStatusCode(statusCode.code);
}
/**
* Prepares a response with the provided status line, binary-encoded
* content type and response.
* The format of response:
* Status-Line (that includes CRLF itself);
* for each header in header do:
* header + CLRF
* done;
* CRLF;
* response-body;
*
* @param statusCode the HTTP response status code
* @param headers the map of response headers (In addition to these, we also
* add the content-length header)
* @param value binary-encoded response content value
*/
public void setResponseWithHeaders(HttpStatusCode statusCode,
@Nullable Map headers,
@Nullable byte[] value) {
byte[] statusLine = statusCode.statusLine;
int valueSize = (value == null) ? 0 : value.length;
byte[] len = stringToBytes(String.valueOf(valueSize));
int size = statusLine.length;
size += CONTENT_LENGTH.length;
size += len.length;
size += TextCommandConstants.RETURN.length;
if (headers != null) {
for (Map.Entry entry : headers.entrySet()) {
size += stringToBytes(entry.getKey() + ": ").length;
size += stringToBytes(entry.getValue().toString()).length;
size += TextCommandConstants.RETURN.length;
}
}
size += TextCommandConstants.RETURN.length;
size += valueSize;
this.response = ByteBuffer.allocate(size);
response.put(statusLine);
response.put(CONTENT_LENGTH);
response.put(len);
response.put(TextCommandConstants.RETURN);
if (headers != null) {
for (Map.Entry entry : headers.entrySet()) {
response.put(stringToBytes(entry.getKey() + ": "));
response.put(stringToBytes(entry.getValue().toString()));
response.put(TextCommandConstants.RETURN);
}
}
response.put(TextCommandConstants.RETURN);
if (value != null) {
response.put(value);
}
response.flip();
setStatusCode(statusCode.code);
}
/**
* Prepares a response with the provided status line, binary-encoded
* content type and response.
*
* @param statusCode the HTTP response status code
* @param contentType binary-encoded response content type
* @param value binary-encoded response content value
*/
public void setResponse(HttpStatusCode statusCode,
@Nullable byte[] contentType,
@Nullable byte[] value) {
byte[] statusLine = statusCode.statusLine;
int valueSize = (value == null) ? 0 : value.length;
byte[] len = stringToBytes(String.valueOf(valueSize));
int size = statusLine.length;
if (contentType != null) {
size += CONTENT_TYPE.length;
size += contentType.length;
size += TextCommandConstants.RETURN.length;
}
size += CONTENT_LENGTH.length;
size += len.length;
size += TextCommandConstants.RETURN.length;
size += TextCommandConstants.RETURN.length;
size += valueSize;
this.response = ByteBuffer.allocate(size);
response.put(statusLine);
if (contentType != null) {
response.put(CONTENT_TYPE);
response.put(contentType);
response.put(TextCommandConstants.RETURN);
}
response.put(CONTENT_LENGTH);
response.put(len);
response.put(TextCommandConstants.RETURN);
response.put(TextCommandConstants.RETURN);
if (value != null) {
response.put(value);
}
response.flip();
setStatusCode(statusCode.code);
}
@Override
public boolean writeTo(ByteBuffer dst) {
copyFromHeapBuffer(response, dst);
return !response.hasRemaining();
}
@Override
public boolean readFrom(ByteBuffer src) {
while (src.hasRemaining()) {
char c = (char) src.get();
if (c == '\n') {
if (nextLine) {
return true;
}
nextLine = true;
} else if (c != '\r') {
nextLine = false;
}
}
return false;
}
@Override
public String toString() {
return "HttpCommand ["
+ type + "]{"
+ "uri='"
+ uri
+ '\''
+ '}'
+ super.toString();
}
public RestCallExecution getExecutionDetails() {
return executionDetails;
}
void setStatusCode(int statusCode) {
int existingStatusCode = executionDetails.getStatusCode();
if (existingStatusCode > 0) {
throw new IllegalStateException("can not set statusCode to " + statusCode + ", it is already " + existingStatusCode);
}
executionDetails.setStatusCode(statusCode);
}
@Override
public void beforeSendResponse(TextCommandService textCommandService) {
RestCallCollector collector = textCommandService.getRestCallCollector();
collector.collectExecution(this);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy