From d3a72b541b13d83d0a5562289c04fb951f3e367d Mon Sep 17 00:00:00 2001 From: JJ Date: Tue, 21 Mar 2023 18:16:05 -0700 Subject: Initial skeleton of support for HTTP --- src/main/model/http/Header.java | 3 ++ src/main/model/http/Http.java | 45 +++++++++++++++++++++++++++ src/main/model/http/HttpRequest.java | 57 +++++++++++++++++++++++++++++++++++ src/main/model/http/HttpResponse.java | 34 +++++++++++++++++++++ src/main/model/util/Uri.java | 38 +++++++++++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 src/main/model/http/Header.java create mode 100644 src/main/model/http/Http.java create mode 100644 src/main/model/http/HttpRequest.java create mode 100644 src/main/model/http/HttpResponse.java create mode 100644 src/main/model/util/Uri.java (limited to 'src/main/model') diff --git a/src/main/model/http/Header.java b/src/main/model/http/Header.java new file mode 100644 index 0000000..74d318c --- /dev/null +++ b/src/main/model/http/Header.java @@ -0,0 +1,3 @@ +package model.http; + +public record Header(String key, String value) {} diff --git a/src/main/model/http/Http.java b/src/main/model/http/Http.java new file mode 100644 index 0000000..9558c79 --- /dev/null +++ b/src/main/model/http/Http.java @@ -0,0 +1,45 @@ +package model.http; + +import model.util.Uri; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.Socket; + +// no static classes?? o_o +// add support for http 0.9 and also http 1.1 as 0.9 +public class Http { + private static final String CRLF = "\r\n"; + + public static HttpResponse fetch(HttpRequest request, int port) throws IOException, HttpResponse.InvalidResponseException { + try (Socket socket = new Socket(request.host(), port); + PrintWriter out = new PrintWriter(socket.getOutputStream(), true); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));) { + out.println(request.raw()); + String bufferOne = ""; + String bufferTwo = ""; + String response = ""; + while (!bufferOne.equals(CRLF) && !bufferTwo.equals(CRLF)) { + bufferTwo = bufferOne; + bufferOne = in.readLine() + CRLF; + response += bufferOne; + } + return HttpResponse.parse(response); + } catch (HttpRequest.MalformedRequestException e) { + System.out.println("Failed to make request: " + e.getMessage()); + return new HttpResponse(600, "Malformed Request"); + } + } + + public static HttpResponse get(Uri location) { + // todo + return null; + } + + public static HttpResponse post(Uri location) { + // todo + return null; + } +} diff --git a/src/main/model/http/HttpRequest.java b/src/main/model/http/HttpRequest.java new file mode 100644 index 0000000..7c27d24 --- /dev/null +++ b/src/main/model/http/HttpRequest.java @@ -0,0 +1,57 @@ +package model.http; + +import model.util.Uri; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public record HttpRequest(String method, String target, String version, ArrayList
headers, String body) { + + private static final String CRLF = "\r\n"; + private static final String DefaultVersion = "HTTP 0.9"; + private static final Header[] DefaultHeaders = {new Header("User-Agent", "apus")}; + + public HttpRequest(String method, String target, String version, ArrayList
headers) { + this(method, target, version, headers, ""); + } + + // note: we disregard the port, that is taken care of by the connecting socket + public HttpRequest(String method, Uri uri, String version, String body) { + // ah, java + this(method, uri.path(), DefaultVersion, new ArrayList<>(Stream.concat(Stream.of(new Header("", "")), Stream.of(DefaultHeaders)).collect(Collectors.toList()))); + } + + public HttpRequest(String method, Uri uri, String version) { + this(method, uri, version, ""); + } + + public HttpRequest(String method, Uri uri) { + this(method, uri, DefaultVersion); + } + + // yeah, i probably should just use a hashmap + public String host() throws MalformedRequestException { + for (Header header : this.headers()) { + if (header.key().equals("Host")) { + return header.value(); + } + } + throw new MalformedRequestException(); + } + + public static class MalformedRequestException extends Exception {} + + public String raw() { + String buffer = ""; + buffer += this.method() + " " + this.target() + " " + this.version() + CRLF; + // hmm maybe i shouldn't use a hashmap + for (Header header : this.headers()) { + buffer += header.key() + " " + header.value() + CRLF; + } + if (!this.body().equals("")) { + buffer += this.body() + CRLF; + } + buffer += CRLF; + return buffer; + } +} diff --git a/src/main/model/http/HttpResponse.java b/src/main/model/http/HttpResponse.java new file mode 100644 index 0000000..65f5bc0 --- /dev/null +++ b/src/main/model/http/HttpResponse.java @@ -0,0 +1,34 @@ +package model.http; + +import java.util.ArrayList; + +public record HttpResponse(String version, int status, String reason, String body, ArrayList
headers) { + // for constructing local error responses + public HttpResponse(int status, String reason) { + this("HTTP 1.1", status, reason, "", new ArrayList
()); + } + + // probably inefficient but eh + public static HttpResponse parse(String response) throws InvalidResponseException { + try { + var split = response.split("\\r\\n\\r\\n"); + var lines = split[0].split("\\r\\n"); + var body = split[1]; + var start = lines[0].split(" ", 3); + var version = start[0]; + var status = Integer.parseInt(start[1]); + var reason = start[2]; + + var headers = new ArrayList
(); + for (int i = 1; i < lines.length; i++) { + split = lines[i].split(": ", 2); + headers.add(new Header(split[0], split[1])); + } + return new HttpResponse(version, status, reason, body, headers); + } catch (IndexOutOfBoundsException | NumberFormatException e) { + throw new InvalidResponseException(); + } + } + + public static class InvalidResponseException extends Exception {} +} diff --git a/src/main/model/util/Uri.java b/src/main/model/util/Uri.java new file mode 100644 index 0000000..b374553 --- /dev/null +++ b/src/main/model/util/Uri.java @@ -0,0 +1,38 @@ +package model.util; + +import java.util.Set; + +public record Uri(String scheme, String authority, String userinfo, String host, int port, String path, String query, String fragment) { + private static final Set GenDelims = Set.of(':', '/', '?', '#', '[', ']', '@'); + private static final Set SubDelims = Set.of('!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '='); + + public Uri parse(String input) { + // todo + return null; + } + + // percent-encode an arbitrary string + public String encode(String input) { + String buffer = ""; + + for (char c : input.toCharArray()) { + if (GenDelims.contains(c) || SubDelims.contains(c)) { + // todo + } else { + buffer += c; + } + } + return buffer; + } +} + +/* + scheme*: string # : + authority*: string # // + userinfo*: string # @ + host*: string # . + port*: int # : + path*: string # / + query*: string # ? + fragment*: string # # +*/ \ No newline at end of file -- cgit v1.2.3-70-g09d2