0

I am working on making a web server with java.

The task is...

  1. The Host header of HTTP/1.1 must be resolvable. For example, the web server implementing the request of a.com and b.com should be able to provide different data depending on the domain.

  2. The following should be manageable in a configuration file. (ex properties, json, xml, yml, etc.)

    2-1. The operating port of the implemented web server must be configurable. (ex 80, 8080)

    2-2. HTTP_DOC_ROOT must be designated for each HTTP/1.1 Host.

    2-3. It should be possible to designate the path of the HTML file on the server to output in case of 403, 404, or 500 errors for each HTTP/1.1 host.

Below is sample code.


import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HttpServer {
    private static final Logger logger = Logger.getLogger(HttpServer.class.getCanonicalName());
    private static final int NUM_THREADS = 50;
    private static final String INDEX_FILE = "index.html";
    private final File rootDirectory;
    private final int port;

    public HttpServer(File rootDirectory, int port) throws IOException {
        if (!rootDirectory.isDirectory()) {
            throw new IOException(rootDirectory
                    + " does not exist as a directory");
        }
        this.rootDirectory = rootDirectory;
        this.port = port;
    }

    public void start() throws IOException {
        ExecutorService pool = Executors.newFixedThreadPool(NUM_THREADS);
        try (ServerSocket server = new ServerSocket(port)) {
            logger.info("Accepting connections on port : " + server.getLocalPort());
            logger.info("Document Root: " + rootDirectory);
            while (true) {
                try {
                    logger.info("Runnable Start ");
                    Socket request = server.accept();

                    Runnable r = new RequestProcessor(rootDirectory, INDEX_FILE, request);
                    logger.info("Runnable r1 : " + r);
                    pool.submit(r);
                    logger.info("Runnable r2 : " + r);
                } catch (IOException ex) {
                    logger.log(Level.WARNING, "Error accepting connection", ex);
                }
            }
        }
    }

    public static void main(String[] args) {
        // get the Document root
        File docroot;
        try {
            docroot = new File(args[0]);
        } catch (ArrayIndexOutOfBoundsException ex) {
            System.out.println("Usage: java JHTTP docroot port : " + ex);
            return;
        }
        // set the port to listen on
        int port;
        try {
            port = Integer.parseInt(args[1]);
            if (port < 0 || port > 65535) port = 80;
        } catch (RuntimeException ex) {
            port = 80;
        }
        try {
            HttpServer webserver = new HttpServer(docroot, port);
            webserver.start();
        } catch (IOException ex) {
            logger.log(Level.SEVERE, "Server could not start", ex);
        }
    }
}
import java.io.*;
import java.net.Socket;
import java.net.URLConnection;
import java.nio.file.Files;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

public class RequestProcessor implements Runnable {
    private final static Logger logger = Logger.getLogger(RequestProcessor.class.getCanonicalName());
    private File rootDirectory;
    private String indexFileName = "index.html";
    private Socket connection;

    public RequestProcessor(File rootDirectory, String indexFileName, Socket connection) {
        if (rootDirectory.isFile()) {
            throw new IllegalArgumentException(
                    "rootDirectory must be a directory, not a file");
        }
        try {
            rootDirectory = rootDirectory.getCanonicalFile();
        } catch (IOException ex) {
        }
        this.rootDirectory = rootDirectory;
        if (indexFileName != null)
            this.indexFileName = indexFileName;
        this.connection = connection;
    }

    @Override
    public void run() {
        // for security checks
        String root = rootDirectory.getPath();
        logger.info("RequestProcessor Start!!!!!!!");
        logger.info(  "root : " + root);
        try {
            OutputStream raw = new BufferedOutputStream(connection.getOutputStream());
            Writer out = new OutputStreamWriter(raw);
            Reader in = new InputStreamReader(new BufferedInputStream(connection.getInputStream()), "UTF-8");
            StringBuilder requestLine = new StringBuilder();
            while (true) {
                int c = in.read();
                if (c == '\r' || c == '\n')
                    break;
                requestLine.append((char) c);
            }
            logger.info(  "requestLine : " + requestLine);
            String get = requestLine.toString();
            logger.info("connection.getRemoteSocketAddress" + connection.getRemoteSocketAddress() + " " + get);
            String[] tokens = get.split("\\s+");
            String method = tokens[0];
            String version = "";
            if (method.equals("GET")) {
                String fileName = tokens[1];
                if (fileName.endsWith("/")) fileName += indexFileName;
                String contentType =
                        URLConnection.getFileNameMap().getContentTypeFor(fileName);
                if (tokens.length > 2) {
                    version = tokens[2];
                }
                File theFile = new File(rootDirectory, fileName.substring(1, fileName.length()));
                if (theFile.canRead()
// Don't let clients outside the document root
                        && theFile.getCanonicalPath().startsWith(root)) {
                    byte[] theData = Files.readAllBytes(theFile.toPath());
                    if (version.startsWith("HTTP/")) { // send a MIME header
                        sendHeader(out, "HTTP/1.0 200 OK", contentType, theData.length);
                    }
                    // send the file; it may be an image or other binary data
                    // so use the underlying output stream
                    // instead of the writer
                    raw.write(theData);
                    raw.flush();
                } else {
                    // can't find the file
                    String body = new StringBuilder("<HTML>\r\n")
                            .append("<HEAD><TITLE>File Not Found</TITLE>\r\n")
                            .append("</HEAD>\r\n")
                            .append("<BODY>")
                            .append("<H1>HTTP Error 404: File Not Found</H1>\r\n")
                            .append("</BODY></HTML>\r\n")
                            .toString();
                    if (version.startsWith("HTTP/")) { // send a MIME header
                        sendHeader(out, "HTTP/1.0 404 File Not Found", "text/html; charset=utf-8", body.length());
                    }
                    out.write(body);
                    out.flush();
                }
            } else {
                // method does not equal "GET"
                String body = new StringBuilder("<HTML>\r\n").append("<HEAD><TITLE>Not Implemented</TITLE>\r\n").append("</HEAD>\r\n")
                        .append("<BODY>")
                        .append("<H1>HTTP Error 501: Not Implemented</H1>\r\n")
                        .append("</BODY></HTML>\r\n").toString();
                if (version.startsWith("HTTP/")) { // send a MIME header
                    sendHeader(out, "HTTP/1.0 501 Not Implemented",
                            "text/html; charset=utf-8", body.length());
                }
                out.write(body);
                out.flush();
            }
        } catch (IOException ex) {
            logger.log(Level.WARNING, "Error talking to " + connection.getRemoteSocketAddress(), ex);
        } finally {
            try {
                connection.close();
            } catch (IOException ex) {
            }
        }
    }

    private void sendHeader(Writer out, String responseCode, String contentType, int length)
            throws IOException {
        out.write(responseCode + "\r\n");
        Date now = new Date();
        out.write("Date: " + now + "\r\n");
        out.write("Server: JHTTP 2.0\r\n");
        out.write("Content-length: " + length + "\r\n");
        out.write("Content-type: " + contentType + "\r\n\r\n");
        out.flush();
    }
}

I searched for network programming, http, and Java socket programming, and also looked for books, so I think I know the concept, but I don't know what to do to convert it into java code. Even after searching, I can't find the code for the host header in the problem, so I'm at a loss.

Midterm review this week, no progress..

My stuck point

  1. I don't even know where the host header is and I don't know how to fix it.
  2. I don't know how to set up the config file in problem #2

Is there any way to implement it in code? And how do developers transfer and use the concepts that came out of the search into code? I don't know what to do if searching doesn't turn up any suitable code.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
othello
  • 1
  • 1
  • @ScaryWombat I don't think that's a correct dupe -- the code the OP has written(?) so far does return the file contents at the requested path, but doesn't go on to read headers. – tgdavies Aug 08 '23 at 04:58
  • You need to explain where you are stuck. Do you understand the code you have so far? Do you understand where header information appears in an HTTP request? When you say "I don't know what to do if searching doesn't turn up any suitable code" I think you should talk to your teacher about getting some extra help. – tgdavies Aug 08 '23 at 04:59
  • @tgdavies I added my stuck point, so please take a look. – othello Aug 08 '23 at 05:18
  • The Host header is in the headers, and the headers are a series of lines ending in a blank line preceding the payload. This question is however far too broad. – user207421 Aug 08 '23 at 05:30
  • In the HTTP protocol the client sends a request as a series of text lines (the request headers followed by a blank line followed by an optional request body) and the server responds similarly with a series of lines (the response headers followed by a blank line followed by the response body). See https://en.wikipedia.org/wiki/HTTP#HTTP/1.1_example_of_request_/_response_transaction for an example – Thomas Kläger Aug 08 '23 at 05:36
  • Here's a description of the HTTP protocol which shows exactly hoe headers appear in a request: https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview Have a look at the `java.util.Properties` class to see how to handle the configuration file. – tgdavies Aug 08 '23 at 05:36
  • @tgdavies I have reopened this question. I think that my dupe was answering his initial task *the web server implementing the request of a.com and b.com should be able to provide different data depending on the domain* In his initial question before edits, this was the crux of his question and did not seem to be coded. – Scary Wombat Aug 08 '23 at 05:58
  • @ScaryWombat maybe I missed something, but I thought that dupe only dealt with the first line of the request. – tgdavies Aug 08 '23 at 06:00
  • @tgdavies Probably I am the one missing something, but my reading of his version one of this question seemed to be how to provide different data based upon requested coming in via different urls. Ah well. – Scary Wombat Aug 08 '23 at 06:02
  • @tgdavies I'm not good at English so I don't know what "dupe" is. However, as ScaryWombat said, I'd like to know how to provide different data based upon requested coming in via different urls. (via config file). – othello Aug 08 '23 at 06:45

1 Answers1

0

After you finished reading request line, you will have the headers. Something like below:

GET /hello.html HTTP/1.0\r\n
Host: a.com\r\n
Date: Tue, 8 Aug 2023 06:16:00 GMT\r\n\r\n

Please note that the order of the headers are not guaranteed. All the headers end when you receive two CR-LF (\r\n).