ActiveJ framework learning -- HTTP for Async I/O

Posted by roopurt18 on Sat, 25 Dec 2021 20:23:20 +0100

2021SC@SDUSC

The last article introduced the main content of HTTP, so let's look at the source code.

First, let's look at the AsyncServlet interface.

  • Basically, it's just an asynchronous function that maps HttpRequest to HttpResponse
  • Predefined AsyncServlet sets (StaticServlet, BlockingServlet, RoutingServlet, etc.) out of the box
  • The functional combination of AsyncServlets is widely supported. RoutingServlet is used to build servlet routes
    • Flexible mapping of HTTP paths and methods to AsyncServlets, including other RoutingServlets
    • Path parameters (such as / users/:id) and relative paths are supported

In short, it is the interface of servlet function, which asynchronously receives HttpRequest, processes it, and then returns HttpResponse.

public interface AsyncServlet {
	@NotNull Promisable<HttpResponse> serve(@NotNull HttpRequest request) throws Exception;

	default @NotNull Promise<HttpResponse> serveAsync(@NotNull HttpRequest request) throws Exception {
		return serve(request).promise();
	}

	static @NotNull AsyncServlet ofBlocking(@NotNull Executor executor, @NotNull BlockingServlet blockingServlet) {
		return request -> request.loadBody()
				.then(() -> Promise.ofBlocking(executor,
						() -> blockingServlet.serve(request)));
	}
}
The method ofBlocking() wraps the given BlockingServlet asynchronously with the given Executor.

BlockingServlet

Blocked version of AsyncServlet #.

public interface BlockingServlet {
	@NotNull HttpResponse serve(@NotNull HttpRequest request) throws Exception;
}

HttpExceptionFormatter

This is the interface for the formatter function of HTTP. It converts unhandled check exceptions that may be returned from the server root servlet and converts them into HTTP error responses. It has the following operations:

The standard formatter maps all exceptions except HttpException to an empty response with a 500 status code. HttpExceptions are mapped to the stack trace of the response and its status code, message, and reason, if specified.

	HttpExceptionFormatter DEFAULT_FORMATTER = e -> {
		HttpResponse response;
		if (e instanceof HttpError) {
			int code = ((HttpError) e).getCode();
			response = HttpResponse.ofCode(code).withHtml(HTTP_ERROR_HTML.replace("{title}", HttpUtils.getHttpErrorTitle(code)).replace("{message}", e.toString()));
		} else {
			response = HttpResponse.ofCode(500).withHtml(INTERNAL_SERVER_ERROR_HTML);
		}
		// default formatter leaks no information about unknown exceptions
		return response
				.withHeader(CACHE_CONTROL, "no-store")
				.withHeader(PRAGMA, "no-cache")
				.withHeader(AGE, "0");
	};

This formatter prints the stack trace of the exception to the HTTP response.

	HttpExceptionFormatter DEBUG_FORMATTER = e ->
			DebugStacktraceRenderer.render(e, e instanceof HttpError ? ((HttpError) e).getCode() : 500)
					.withHeader(CACHE_CONTROL, "no-store")
					.withHeader(PRAGMA, "no-cache")
					.withHeader(AGE, "0");

This formatter is default_ Format} or debug_ One of the formats, depending on whether the application is launched from the IntelliJ IDE.

	HttpExceptionFormatter COMMON_FORMATTER =
			System.getProperty("java.class.path", "").contains("idea_rt.jar") ?
					DEBUG_FORMATTER :
					DEFAULT_FORMATTER;

HttpPathPart

The tag interface is implemented by enumerations that store commands to attach to the HTTP request path

public interface HttpPathPart {

	@Override
	String toString();
}

IAsyncHttpClient

Interface to asynchronous HTTP clients. It is as simple as an asynchronous function. It accepts HttpRequest and returns an HttpResponse for it, so it is basically the reciprocal of AsyncServlet.

public interface IAsyncHttpClient {
	Promise<HttpResponse> request(HttpRequest request);
}

IAsyncWebSocketClient

public interface IAsyncWebSocketClient {
	Promise<WebSocket> webSocketRequest(HttpRequest request);
}

WebSocket

An abstract class that allows sending and receiving network socket data in the form of frames or messages. Because this interface extends the AsyncCloseable interface, you can close the network socket with the appropriate exception. If the exception is an instance of WebSocketException, it will be converted to an appropriate shutdown frame with the corresponding shutdown code and shutdown reason. Any other exception will result in a close frame with close codes 1001 ^ and 1011 ^ on the client and server.

The specific methods include:

@NotNull Promise<Message> readMessage();

Returns the promise of a complete network socket message consisting of one or more data frames. If the other party sends a close frame of code 1000 between other messages, null promise may be returned. A null} message indicates the end of the flow. It is illegal to call this method after calling readFrame(), without returning the last frame of the message.

	@NotNull Promise<Frame> readFrame();

Returns the promise of a network socket data frame. It may contain the entire Web socket message or just a part of the message. Any UTF-8 validation of the text framework should be done by the user of this method. If the other party sends a closed code frame 1000, a null may be returned. A null} frame indicates the end of the stream. All readXXX # methods should be called consecutively. It is illegal to call readMessage() after calling this method and not returning the last frame of the message.

	default @NotNull ChannelSupplier<Frame> frameReadChannel() {
		return ChannelSupplier.of(this::readFrame, this);
	}

Allows you to get a shortcut to the channel provider of the Frame #.

 

	default @NotNull ChannelSupplier<Message> messageReadChannel() {
		return ChannelSupplier.of(this::readMessage, this);
	}

Allow to get the shortcut of the channel provider of Message +,

	@NotNull Promise<Void> writeMessage(@Nullable Message msg);

A method of sending network socket message. If null} is passed to this method, the flow ends. It is equivalent to sending a close frame with a close code of {1000, indicating that the web socket connection is closed normally.

	@NotNull Promise<Void> writeFrame(@Nullable Frame frame);

A method of sending network socket data frame. If null is passed to this method, the flow ends. It is equivalent to sending a close frame with a close code of {1000, indicating that the web socket connection is closed normally.

	default @NotNull ChannelConsumer<Frame> frameWriteChannel() {
		return ChannelConsumer.of(this::writeFrame, this)
				.withAcknowledgement(ack -> ack.then(() -> writeFrame(null)));
	}

Allows you to get a shortcut to the channel user of the Frame #.

	default @NotNull ChannelConsumer<Message> messageWriteChannel() {
		return ChannelConsumer.of(this::writeMessage, this)
				.withAcknowledgement(ack -> ack
						.then(() -> writeMessage(null)));
	}

Allows you to get a shortcut to the channel consumer of Message.

	HttpRequest getRequest();

A method for checking HTTP requests associated with this Web socket.

	HttpRequest getRequest();

A method for checking HTTP requests associated with this Web socket.

	HttpResponse getResponse();

A method to check the HTTP response associated with this Web socket.

	boolean isClosed();

Indicates whether this socket has been closed

Finally, the interface contains two internal classes Message and Frame.

  • Message: the representation of a complete Web socket message. It may contain text or binary data.
  • Frame: the representation of network socket data frame. It can be one of text, binary, or continuous types.
    	final class Message implements Recyclable {
    		private final MessageType type;
    		private final @Nullable ByteBuf binaryPayload;
    		private final @Nullable String textPayload;
    
    		Message(MessageType type, @Nullable ByteBuf binaryPayload, @Nullable String textPayload) {
    			this.type = type;
    			this.textPayload = textPayload;
    			this.binaryPayload = binaryPayload;
    		}
    
    		public static Message text(String payload) {
    			return new Message(TEXT, null, payload);
    		}
    
    		public static Message binary(ByteBuf payload) {
    			return new Message(BINARY, payload, null);
    		}
    
    		public MessageType getType() {
    			return type;
    		}
    
    		public ByteBuf getBuf() {
    			return checkNotNull(binaryPayload);
    		}
    
    		public String getText() {
    			return checkNotNull(textPayload);
    		}
    
    		@Override
    		public void recycle() {
    			if (binaryPayload != null) {
    				binaryPayload.recycle();
    			}
    		}
    
    		public enum MessageType {
    			TEXT, BINARY
    		}
    	}
    	final class Frame implements Recyclable {
    		private final FrameType type;
    		private final ByteBuf payload;
    		private final boolean isLastFrame;
    
    		Frame(FrameType type, ByteBuf payload, boolean isLastFrame) {
    			this.type = type;
    			this.payload = payload;
    			this.isLastFrame = isLastFrame;
    		}
    
    		public static Frame text(ByteBuf buf) {
    			return new Frame(FrameType.TEXT, buf, true);
    		}
    
    		public static Frame text(ByteBuf buf, boolean isLastFrame) {
    			return new Frame(FrameType.TEXT, buf, isLastFrame);
    		}
    
    		public static Frame binary(ByteBuf buf) {
    			return new Frame(FrameType.BINARY, buf, true);
    		}
    
    		public static Frame binary(ByteBuf buf, boolean isLastFrame) {
    			return new Frame(FrameType.BINARY, buf, isLastFrame);
    		}
    
    		public static Frame next(ByteBuf buf, boolean isLastFrame) {
    			return new Frame(CONTINUATION, buf, isLastFrame);
    		}
    
    		public FrameType getType() {
    			return type;
    		}
    
    		public ByteBuf getPayload() {
    			return payload;
    		}
    
    		public boolean isLastFrame() {
    			return isLastFrame;
    		}
    
    		@Override
    		public void recycle() {
    			payload.recycle();
    		}
    
    		public enum FrameType {
    			TEXT, BINARY, CONTINUATION
    		}
    	}

Topics: Java http