I'm working on a proxy service that will run on top of a Java application in order to manipulate some headers. I've just completed the first step and that is intercepting the request message and storing them.
This code features only the storage part, and not the edit and send part.
Some information about HTTP requests:
- A request consists of header lines, seperated by a newline.
- The header is terminated by an extra newline.
- After that possibly there is additional content, the message payload.
Some assumptions I make in this code:
- It assumes the data is input over a
Socket
. - It works for both
\n
and\r\n
newlines. - It assumes the
US-ASCII
character set for header. - It can only deal with either no
Content-Length
or with normal content length, it does not know about chunked data (yet).
Extra caution may be paid to if the trade-off of bloated code due to increased low level performance versus concise code is worth it.
public final class Converter {
private Converter() {
throw new UnsupportedOperationException();
}
private static final int LINE_FEED_BYTE = 10; //'\n'
private static final int CARRIAGE_RETURN_BYTE = 13; //'\r'
public static void socketToMessages(final Socket socket, final BlockingQueue<Message> messages) {
listeningLoop:
while (true) {
List<String> headers = new ArrayList<>();
int newlineCount = 0;
StringBuilder headerStringBuilder = new StringBuilder();
try {
byte[] byteArray = new byte[1];
InputStream inputStream = socket.getInputStream();
boolean readingHeader = true;
while (readingHeader) {
int b = inputStream.read();
if (b == -1) {
break listeningLoop;
}
else if (b == LINE_FEED_BYTE) {
newlineCount++;
String header = headerStringBuilder.toString();
headers.add(header);
headerStringBuilder.setLength(0);
if (newlineCount == 2) {
readingHeader = false;
}
}
else if (b == CARRIAGE_RETURN_BYTE) {
//do nothing
}
else {
newlineCount = 0;
byteArray[0] = (byte)b;
headerStringBuilder.append(new String(byteArray, StandardCharsets.US_ASCII));
}
}
byte[] contentBytes = new byte[0];
Optional<String> contentLengthOptional = Message.getHeaderValue(headers, "Content-Length");
if (contentLengthOptional.isPresent()) {
try {
int contentLength = Integer.parseInt(contentLengthOptional.get());
contentBytes = new byte[contentLength];
int totalReadBytes = 0;
while (totalReadBytes < contentLength) {
int readBytes = inputStream.read(contentBytes, totalReadBytes, contentLength - totalReadBytes);
if (readBytes == -1) {
break listeningLoop;
}
totalReadBytes += readBytes;
}
} catch (NumberFormatException ex) {
//do nothing
}
}
messages.add(new Message(headers, contentBytes));
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
}
}
public class Message {
private final List<String> headers;
private final byte[] content;
public Message(final List<String> headers, final byte[] content) {
this.headers = Objects.requireNonNull(headers, "headers");
this.content = Objects.requireNonNull(content, "content");
}
public List<String> getHeaders() {
return headers;
}
public byte[] getContent() {
return content;
}
public static Optional<String> getHeaderValue(final List<String> headers, final String key) {
Objects.requireNonNull(headers, "headers");
Objects.requireNonNull(key, "key");
for (String header : headers) {
String[] words = header.split(" ");
if (words[0].contains(":")) {
String foundKey = words[0].replace(":", "");
if (foundKey.equals(key)) {
return Optional.of(String.join(" ", Arrays.asList(words).subList(1, words.length)));
}
}
}
return Optional.empty();
}
}
An example request is:
GET http://top.secret.mil/secretservices/services/TopSecretServices?wsdl HTTP/1.1
User-Agent: Java/1.8.0
Host: top.secret.mil
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Proxy-Connection: keep-alive
Example calling code is:
BlockingQueue<Message> messages = new LinkedBlockingQueue<>();
new Thread(() -> Converter.socketToMessages(socket, messages)).start();
while (true) {
try {
Message message = messages.take();
message.getHeaders().forEach(System.out::println);
System.out.println(message.getContent().length);
} catch (InterruptedException ex) {
Thread.interrupted();
}
}
I'd like a review on all aspects of this code.