
objectos.way.HttpServer Maven / Gradle / Ivy
/*
* Copyright (C) 2023-2024 Objectos Software LTDA.
*
* 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 objectos.way;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.time.Clock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import objectos.way.HttpExchange.ParseStatus;
/**
* The Objectos Way {@link HttpServer} implementation.
*/
final class HttpServer implements Http.Server, Runnable {
record Notes(
Note.Ref1 started,
Note.Ref0 stopped,
Note.Ref1 ioError,
Note.Ref1 loopError,
Note.Ref1 internalServerError
) implements Http.Server.Notes {
static Notes get() {
Class> s = Http.Server.class;
return new Notes(
Note.Ref1.create(s, "Started", Note.INFO),
Note.Ref0.create(s, "Stopped", Note.INFO),
Note.Ref1.create(s, "I/O error", Note.ERROR),
Note.Ref1.create(s, "Loop error", Note.ERROR),
Note.Ref1.create(s, "Internal server error", Note.ERROR)
);
}
}
private final Notes notes = Notes.get();
private final int bufferSizeInitial;
private final int bufferSizeMax;
private final Clock clock;
private final Http.HandlerFactory factory;
private final Note.Sink noteSink;
private final int port;
private ServerSocket serverSocket;
private Thread thread;
public HttpServer(HttpServerConfig builder) {
bufferSizeInitial = builder.bufferSizeInitial;
bufferSizeMax = builder.bufferSizeMax;
clock = builder.clock;
factory = builder.factory;
noteSink = builder.noteSink;
port = builder.port;
}
@Override
public final void start() throws IOException {
if (thread != null) {
throw new IllegalStateException(
"The service has already been started."
);
}
InetAddress address;
address = InetAddress.getLoopbackAddress();
InetSocketAddress socketAddress;
socketAddress = new InetSocketAddress(address, port);
serverSocket = new ServerSocket();
serverSocket.bind(socketAddress);
thread = Thread.ofPlatform().name("HTTP").start(this);
}
@Override
public final InetAddress address() {
checkStarted();
return serverSocket.getInetAddress();
}
@Override
public final int port() {
checkStarted();
return serverSocket.getLocalPort();
}
private void checkStarted() {
if (thread == null) {
throw new IllegalStateException(
"Cannot query this service: service is not running."
);
}
}
@Override
public final void close() throws IOException {
if (thread == null) {
throw new IllegalStateException(
"Cannot stop this service: service is not running."
);
}
try {
thread.interrupt();
} finally {
try {
serverSocket.close();
} finally {
thread = null;
serverSocket = null;
}
}
}
@Override
public final void run() {
ThreadFactory factory;
factory = Thread.ofVirtual().name("http-", 1).factory();
try (ExecutorService executor = Executors.newThreadPerTaskExecutor(factory)) {
noteSink.send(notes.started, serverSocket);
while (!Thread.currentThread().isInterrupted()) {
Socket socket;
socket = serverSocket.accept();
Runnable task;
task = new ExchangeTask(socket);
executor.submit(task);
}
noteSink.send(notes.stopped);
} catch (SocketException e) {
if (serverSocket != null && !serverSocket.isClosed()) {
onIOException(e);
}
} catch (IOException e) {
onIOException(e);
}
}
private void onIOException(IOException e) {
noteSink.send(notes.ioError, e);
}
private class ExchangeTask implements Runnable {
private final Socket socket;
public ExchangeTask(Socket socket) {
this.socket = socket;
}
@Override
public final void run() {
try (HttpExchange http = new HttpExchange(socket, bufferSizeInitial, bufferSizeMax, clock, noteSink)) {
while (!Thread.currentThread().isInterrupted()) {
ParseStatus parse;
parse = http.parse();
if (parse == ParseStatus.EOF) {
break;
}
if (parse.isError()) {
throw new UnsupportedOperationException("Implement me");
}
try {
Http.Handler handler;
handler = factory.create();
handler.handle(http);
} catch (Http.AbstractHandlerException ex) {
ex.handle(http);
} catch (HttpExchange.SendException ex) {
IOException cause;
cause = ex.getCause();
noteSink.send(notes.ioError, cause);
break; // could not send response. End this exchange
} catch (Throwable t) {
noteSink.send(notes.internalServerError, t);
if (!http.processed()) {
http.internalServerError(t);
}
}
if (!http.keepAlive()) {
break;
}
}
} catch (IOException e) {
noteSink.send(notes.ioError, e);
} catch (Throwable t) {
noteSink.send(notes.loopError, t);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy