aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJJ2023-03-22 01:16:05 +0000
committerJJ2023-03-22 01:16:05 +0000
commitd3a72b541b13d83d0a5562289c04fb951f3e367d (patch)
treee45c23569860d0c31e15582dc7504ef2209782d8
parentf575e555b40629770dcec41eb1ec2f46cc645566 (diff)
Initial skeleton of support for HTTP
-rw-r--r--src/main/model/http/Header.java3
-rw-r--r--src/main/model/http/Http.java45
-rw-r--r--src/main/model/http/HttpRequest.java57
-rw-r--r--src/main/model/http/HttpResponse.java34
-rw-r--r--src/main/model/util/Uri.java38
5 files changed, 177 insertions, 0 deletions
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<Header> 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<Header> 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<Header> headers) {
+ // for constructing local error responses
+ public HttpResponse(int status, String reason) {
+ this("HTTP 1.1", status, reason, "", new ArrayList<Header>());
+ }
+
+ // 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<Header>();
+ 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<Character> GenDelims = Set.of(':', '/', '?', '#', '[', ']', '@');
+ private static final Set<Character> 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