-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: clean up of the code base and multipart form parsing enabled
- Loading branch information
1 parent
7d733e6
commit 7164e60
Showing
4 changed files
with
235 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 + '\'' + | ||
'}'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
src/main/java/com/amberj/net/httpserver/HttpRequestUtil.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
103
src/main/java/com/amberj/net/httpserver/MultipartFormDataParser.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |