TransWikia.com

Parsing HTTP request for webserver implementation

Code Review Asked by Campos Ilya on December 17, 2021

I’m writing a web server from scratch on pure Java in learning purpose. As a part of request handling, I worked on a class to parse HTTP request, I expect from it to be able to do next things:

  • extract http method, headers and body from request
  • return BAD REQUEST in case of malformed request
  • response with error on client timeout if client is not sending data and connection is hanging too long

The result is:

class HttpRequestParser {
    private BufferedReader in;
    private int serverPort;
    private HttpMethods method;
    private InetAddress clientAddr;
    private InetAddress serverAddr;
    private URL url;
    private HashMap<String, String> collectedHeaders;
    private long parsingStartTime;

    HttpRequestParser(Socket clientSock) throws IOException {
        this.in = new BufferedReader(new InputStreamReader(clientSock.getInputStream()));

        this.clientAddr = clientSock.getLocalAddress();
        this.serverAddr = clientSock.getInetAddress();
        this.serverPort = clientSock.getLocalPort();
        this.collectedHeaders = new HashMap<>();
    }

    HttpOneZeroRequest parseAll() throws IOException {
        return this.parseAll(this.in);
    }

    private HttpOneZeroRequest parseAll(BufferedReader inputStream) throws IOException {
        this.parsingStartTime = System.currentTimeMillis();

        this.waitUntilClientInputReady();
        String line = inputStream.readLine();
        this.parseTopHeader(line);
        this.waitUntilClientInputReady();
        while (!(line = inputStream.readLine().trim()).equals("")) {
            this.parseCommonHeader(line);
            this.waitUntilClientInputReady();
        }

        byte[] body = {};
        if (this.method == HttpMethods.POST || this.method == HttpMethods.PUT || this.method == HttpMethods.PATCH) {
            body = this.readBody();
        }
        return this.prepareRequest(body);
    }

    private void parseTopHeader(String line) throws HttpException {
        String[] parts = line.split("\s+", 3);
        if (parts.length != 3
                || !parts[1].startsWith("/")
                || !parts[2].toUpperCase().startsWith("HTTP/")) {
            throw new BadRequest();
        }

        try {
            this.url = new URL("http://" + this.getServerNetloc() + parts[1]);
        } catch (MalformedURLException e) {
            throw new BadRequest();
        }

        this.method = HttpMethods.cleanMethod(parts[0]);
    }

    private void parseCommonHeader(String line) throws HttpException {
        String[] pair = line.split(":", 2);
        if (pair.length != 2) {
            throw new BadRequest();
        }
        String name = pair[0].replaceAll(" ", "");
        String value = pair[1].trim();
        this.collectedHeaders.put(name, value);
    }

    private byte[] readBody() throws HttpException, IOException {
        ByteArrayOutputStream bodyStream = new ByteArrayOutputStream();
        int contentLength = 0; // for now content without Content-Length header are ignored
        if (this.collectedHeaders.containsKey("Content-Length")) {
            String valueStr = this.collectedHeaders.get("Content-Length");
            try {
                contentLength = Integer.valueOf(valueStr);
            } catch (NumberFormatException e) {
                throw new BadRequest();
            }
        }

        int nextByte;
        while (bodyStream.size() < contentLength) {
            this.waitUntilClientInputReady();
            nextByte = this.in.read();
            bodyStream.write(nextByte);
        }
        return bodyStream.toByteArray();
    }

    private String getServerNetloc() {
        String hostname = this.serverAddr.getHostName();
        return hostname + ((this.serverPort != 80) ? (":" + this.serverPort) : "");
    }

    private HttpOneZeroRequest prepareRequest(byte[] body) {
        return new HttpOneZeroRequest(
            this.method, this.url, this.collectedHeaders, body,
            this.clientAddr, this.serverAddr, this.serverPort);
    }

    private void waitUntilClientInputReady() throws IOException {
        long timeout = (long)(120 * 1000);
        while (!this.in.ready()) {
            if (System.currentTimeMillis() - this.parsingStartTime > timeout) {
                throw new RequestTimeout();
            }
        }
    }
}

Can you, please, kindly review my implementation, and give some advises about code formatting and optimization? I have poor (close to zero) experience at Java programming and I’m not sure if I choose proper approach to implement certain parts, especially handling client timeout and extract urls…

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP