Skip to content

Commit

Permalink
feat: clean up of the code base and multipart form parsing enabled
Browse files Browse the repository at this point in the history
  • Loading branch information
akashKarmakar02 committed Jun 6, 2024
1 parent 7d733e6 commit 7164e60
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 2 deletions.
15 changes: 14 additions & 1 deletion src/main/java/com/amberj/net/http/HttpRequest.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
package com.amberj.net.http;

import java.net.URI;
import java.util.List;
import java.util.Map;

public record HttpRequest(Map<String, Object> body, Map<String, String> pathParams) {
public record HttpRequest(
Map<String, Object> body,
Map<String, String> pathParams,
Map<String, List<String>> queryParams,
Map<String, List<String>> header,
String method,
URI uri
) {

@Override
public String toString() {
return "HttpRequest{" +
"body=" + body +
", pathParams=" + pathParams +
", queryParams=" + queryParams +
", header=" + header +
", method='" + method + '\'' +
", uri='" + uri + '\'' +
'}';
}
}
12 changes: 11 additions & 1 deletion src/main/java/com/amberj/net/http/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ public class HttpResponse {
private final JinjavaTemplating templatingEngine;
private static FileSystem jarFileSystem;
private String contentType;
private boolean isMethodAllowed;

public HttpResponse() {
templatingEngine = new JinjavaTemplating();
status = 200;
this.isMethodAllowed = true;
}

public void render(String template, Data data) {
Expand Down Expand Up @@ -90,7 +92,7 @@ public void json(Data data) {
response = data.toJson();
}

public void setResponse(String response) {
public void write(String response) {
contentType = "text/html";
this.response = response;
}
Expand All @@ -110,6 +112,10 @@ public int getStatus() {
return status;
}

public boolean isMethodAllowed() {
return isMethodAllowed;
}

public String getContentType() {
return this.contentType;
}
Expand All @@ -121,4 +127,8 @@ public void redirect(String url) {
public String getRedirectURL() {
return this.redirectURL;
}

public void methodNotAllowed() {
this.isMethodAllowed = false;
}
}
107 changes: 107 additions & 0 deletions src/main/java/com/amberj/net/httpserver/HttpRequestUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.amberj.net.httpserver;

import com.amberj.net.http.HttpRequest;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.net.httpserver.HttpExchange;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;

import static java.lang.System.out;

class HttpRequestUtil {
static HttpRequest getHttpRequest(HttpExchange exchange, List<String> paramsList, ArrayList<String> pathParams) throws IOException {
Map<String, Object> body;
if (exchange.getRequestMethod().equalsIgnoreCase("POST") || exchange.getRequestMethod().equalsIgnoreCase("PUT")) {
body = getBody(exchange);
} else {
body = new HashMap<>();
}
var params = getPathParams(pathParams, paramsList);
var header = getHeader(exchange);
var queryParams = getQueryParams(exchange);

return new HttpRequest(body, params, queryParams, header, exchange.getRequestMethod(), exchange.getRequestURI());
}

private static Map<String, Object> getBody(HttpExchange exchange) throws IOException {
var inputStream = exchange.getRequestBody();
var contentType = exchange.getRequestHeaders().get("Content-Type");
Map<String, Object> postData;
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
if (contentType == null) {
return new HashMap<>();
}

if (Objects.equals(contentType.getFirst(), "application/x-www-form-urlencoded")) {
String line;
postData = new HashMap<>();
while ((line = reader.readLine()) != null) {
String[] params = line.split("&");
for (String param : params) {
String[] keyValue = param.split("=");
if (keyValue.length == 2) {
String key = keyValue[0];
String value = java.net.URLDecoder.decode(keyValue[1], StandardCharsets.UTF_8);
postData.put(key, value);
}
}
}
} else if (Objects.equals(contentType.getFirst(), "application/json")) {
ObjectMapper objectMapper = new ObjectMapper();
StringBuilder jsonString = new StringBuilder();
for (var line: reader.lines().toArray()) {
jsonString.append(line);
}
postData = objectMapper.readValue(jsonString.toString(), new TypeReference<>() {});
} else if (contentType.getFirst().startsWith("multipart/form-data")) {
postData = MultipartFormDataParser.parseMultipartForm(exchange);
} else {
postData = new HashMap<>();
}
return postData;
}

private static Map<String, String> getPathParams(ArrayList<String> pathParams, List<String> params) {
var data = new HashMap<String, String>();
if (!pathParams.isEmpty()) {
for (int i = 0; i < pathParams.size(); i++) {
var key = pathParams.get(i).split(":")[0];
data.put(key, params.get(i));
}
}
return data;
}

private static Map<String, List<String>> getQueryParams(HttpExchange exchange) {
var requestUri = exchange.getRequestURI();
String query = requestUri.getQuery();
Map<String, List<String>> queryParams = new HashMap<>();

if (query == null || query.isEmpty()) {
return queryParams;
}

String[] pairs = query.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key = idx > 0 ? URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8) : pair;
String value = idx > 0 && pair.length() > idx + 1 ? URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8) : null;

queryParams.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
}

return queryParams;
}

private static Map<String, List<String>> getHeader(HttpExchange exchange) {
var headers = exchange.getRequestHeaders();

return new HashMap<>(headers);
}
}
103 changes: 103 additions & 0 deletions src/main/java/com/amberj/net/httpserver/MultipartFormDataParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.amberj.net.httpserver;
import com.sun.net.httpserver.HttpExchange;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class MultipartFormDataParser {

public static Map<String, Object> parseMultipartForm(HttpExchange exchange) throws IOException {
String contentType = exchange.getRequestHeaders().getFirst("Content-Type");
if (contentType == null || !contentType.startsWith("multipart/form-data")) {
throw new IllegalArgumentException("Request is not multipart/form-data");
}

String boundary = extractBoundary(contentType);
byte[] requestBody = readRequestBody(exchange);

return parseParts(new String(requestBody, StandardCharsets.UTF_8), boundary);
}

private static String extractBoundary(String contentType) {
String[] parts = contentType.split(";");
for (String part : parts) {
part = part.trim();
if (part.startsWith("boundary=")) {
return part.substring(9);
}
}
throw new IllegalArgumentException("Content-Type does not contain boundary");
}

private static Map<String, Object> parseParts(String body, String boundary) throws IOException {
Map<String, Object> formData = new HashMap<>();
String[] parts = body.split("--" + boundary);

for (String part : parts) {
if (part.trim().isEmpty() || part.equals("--")) {
continue;
}

int headersEndIndex = part.indexOf("\r\n\r\n");
if (headersEndIndex == -1) {
continue;
}

String headersPart = part.substring(0, headersEndIndex);
String bodyPart = part.substring(headersEndIndex + 4).trim();

String[] headers = headersPart.split("\r\n");
String fieldName = null;
String fileName = null;
for (String header : headers) {
if (header.startsWith("Content-Disposition:")) {
String[] elements = header.split(";");
for (String element : elements) {
element = element.trim();
if (element.startsWith("name=\"")) {
fieldName = element.substring(6, element.length() - 1);
} else if (element.startsWith("filename=\"")) {
fileName = element.substring(10, element.length() - 1);
}
}
}
}

if (fieldName != null) {
if (fileName == null) {
// This is a form field
formData.put(fieldName, bodyPart);
} else {
// This is a file field
// Save the file with its original name
File file = saveFile(bodyPart, fileName);
formData.put(fieldName, file);
}
}
}

return formData;
}

private static File saveFile(String fileContent, String fileName) throws IOException {
File file = new File(System.getProperty("java.io.tmpdir"), fileName);
file.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(fileContent.getBytes());
}
return file;
}

private static byte[] readRequestBody(HttpExchange exchange) throws IOException {
InputStream inputStream = exchange.getRequestBody();
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
return result.toByteArray();
}
}

0 comments on commit 7164e60

Please sign in to comment.