MULTIPART UPLOAD WORKS
This commit is contained in:
35
src/main/java/io/github/lumijiez/core/http/HttpFileItem.java
Normal file
35
src/main/java/io/github/lumijiez/core/http/HttpFileItem.java
Normal file
@@ -0,0 +1,35 @@
|
||||
package io.github.lumijiez.core.http;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class HttpFileItem {
|
||||
private final String fileName;
|
||||
private final String contentType;
|
||||
private final byte[] content;
|
||||
|
||||
public HttpFileItem(String fileName, String contentType, byte[] content) {
|
||||
this.fileName = fileName;
|
||||
this.contentType = contentType;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public byte[] getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public void saveTo(File destination) throws IOException {
|
||||
try (FileOutputStream fos = new FileOutputStream(destination)) {
|
||||
fos.write(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package io.github.lumijiez.core.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpMultipartData {
|
||||
private final Map<String, String> fields = new HashMap<>();
|
||||
private final Map<String, HttpFileItem> files = new HashMap<>();
|
||||
|
||||
public void addField(String name, String value) {
|
||||
fields.put(name, value);
|
||||
}
|
||||
|
||||
public void addFile(String name, HttpFileItem file) {
|
||||
files.put(name, file);
|
||||
}
|
||||
|
||||
public String getField(String name) {
|
||||
return fields.get(name);
|
||||
}
|
||||
|
||||
public HttpFileItem getFile(String name) {
|
||||
return files.get(name);
|
||||
}
|
||||
|
||||
public Map<String, String> getFields() {
|
||||
return Collections.unmodifiableMap(fields);
|
||||
}
|
||||
|
||||
public Map<String, HttpFileItem> getFiles() {
|
||||
return Collections.unmodifiableMap(files);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,62 +1,127 @@
|
||||
package io.github.lumijiez.core.http;
|
||||
|
||||
import io.github.lumijiez.core.logging.Logger;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpMultipartParser {
|
||||
private final String boundary;
|
||||
private final BufferedReader reader;
|
||||
private final Map<String, String> parts = new HashMap<>();
|
||||
private final String boundary;
|
||||
private final HttpMultipartData multipartData;
|
||||
private static final int MAX_BUFFER_SIZE = 1024 * 1024; // 1MB
|
||||
|
||||
public HttpMultipartParser(BufferedReader reader, String contentType) {
|
||||
this.reader = reader;
|
||||
this.boundary = extractBoundary(contentType);
|
||||
this.boundary = "--" + extractBoundary(contentType);
|
||||
this.multipartData = new HttpMultipartData();
|
||||
Logger.debug("HTTP", "Initialized parser with boundary: " + this.boundary);
|
||||
}
|
||||
|
||||
private String extractBoundary(String contentType) {
|
||||
String[] parts = contentType.split(";");
|
||||
for (String part : parts) {
|
||||
if (part.trim().startsWith("boundary=")) {
|
||||
return "--" + part.split("=")[1].trim();
|
||||
part = part.trim();
|
||||
if (part.startsWith("boundary=")) {
|
||||
String trim = part.substring(("boundary=".length())).trim();
|
||||
Logger.debug("HTTP", "Boundary: " + trim);
|
||||
return trim;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, String> parse() throws IOException {
|
||||
public HttpMultipartData parse() throws IOException {
|
||||
String line;
|
||||
StringBuilder content = new StringBuilder();
|
||||
String currentName = null;
|
||||
StringBuilder currentPart = new StringBuilder();
|
||||
Map<String, String> currentHeaders = new HashMap<>();
|
||||
boolean isReadingHeaders = false;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
Logger.debug("HTTP", "Reading line: '" + line + "'");
|
||||
|
||||
line = line.trim();
|
||||
|
||||
if (line.startsWith(boundary + "--")) {
|
||||
if (!currentHeaders.isEmpty() && !currentPart.isEmpty()) {
|
||||
processContent(currentHeaders, currentPart.toString());
|
||||
}
|
||||
Logger.debug("HTTP", "Found end boundary, finishing parse");
|
||||
break;
|
||||
}
|
||||
|
||||
if (line.startsWith(boundary)) {
|
||||
if (currentName != null) {
|
||||
parts.put(currentName, content.toString().trim());
|
||||
content = new StringBuilder();
|
||||
if (!currentHeaders.isEmpty() && !currentPart.isEmpty()) {
|
||||
processContent(currentHeaders, currentPart.toString());
|
||||
}
|
||||
currentPart.setLength(0);
|
||||
currentHeaders.clear();
|
||||
isReadingHeaders = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
while ((line = reader.readLine()) != null && !line.isEmpty()) {
|
||||
if (line.toLowerCase().startsWith("content-disposition:")) {
|
||||
currentName = extractFieldName(line);
|
||||
if (isReadingHeaders) {
|
||||
if (line.isEmpty()) {
|
||||
isReadingHeaders = false;
|
||||
continue;
|
||||
}
|
||||
int separator = line.indexOf(':');
|
||||
if (separator > 0) {
|
||||
String headerName = line.substring(0, separator).trim().toLowerCase();
|
||||
String headerValue = line.substring(separator + 1).trim();
|
||||
currentHeaders.put(headerName, headerValue);
|
||||
Logger.debug("HTTP", "Found header: " + headerName + " = " + headerValue);
|
||||
}
|
||||
} else if (!line.equals(boundary + "--")) {
|
||||
content.append(line).append("\n");
|
||||
} else {
|
||||
currentPart.append(line).append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
return parts;
|
||||
return multipartData;
|
||||
}
|
||||
|
||||
private String extractFieldName(String header) {
|
||||
String[] parts = header.split(";");
|
||||
private void processContent(Map<String, String> headers, String content) {
|
||||
String contentDisposition = headers.get("content-disposition");
|
||||
if (contentDisposition == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, String> dispositionParams = parseContentDisposition(contentDisposition);
|
||||
String name = dispositionParams.get("name");
|
||||
String fileName = dispositionParams.get("filename");
|
||||
|
||||
if (fileName != null) {
|
||||
String contentType = headers.getOrDefault("content-type", "application/octet-stream");
|
||||
if (content.endsWith("\r\n")) {
|
||||
content = content.substring(0, content.length() - 2);
|
||||
}
|
||||
byte[] fileContent = content.getBytes(StandardCharsets.UTF_8);
|
||||
HttpFileItem fileItem = new HttpFileItem(fileName, contentType, fileContent);
|
||||
multipartData.addFile(name, fileItem);
|
||||
Logger.debug("HTTP", "Added file: " + name + ", filename: " + fileName);
|
||||
} else {
|
||||
multipartData.addField(name, content.trim());
|
||||
Logger.debug("HTTP", "Added field: " + name);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> parseContentDisposition(String contentDisposition) {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
String[] parts = contentDisposition.split(";");
|
||||
|
||||
for (String part : parts) {
|
||||
if (part.trim().startsWith("name=")) {
|
||||
return part.split("=")[1].trim().replace("\"", "");
|
||||
part = part.trim();
|
||||
if (part.contains("=")) {
|
||||
String[] keyValue = part.split("=", 2);
|
||||
String key = keyValue[0].trim();
|
||||
String value = keyValue[1].trim().replace("\"", "");
|
||||
params.put(key, value);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ public class HttpRequest {
|
||||
private UrlParser urlParser;
|
||||
private HttpRequestBody body;
|
||||
private Map<String, String> formData;
|
||||
private HttpMultipartData multipartData;
|
||||
private final Map<String, String> cookies;
|
||||
|
||||
public HttpRequest(BufferedReader in) throws IOException {
|
||||
@@ -115,6 +116,14 @@ public class HttpRequest {
|
||||
int contentLength = Integer.parseInt(headers.get("content-length"));
|
||||
String contentType = headers.getOrDefault("content-type", "text/plain");
|
||||
|
||||
if (contentType.startsWith("multipart/form-data")) {
|
||||
Logger.debug("CONTENT TYPE", contentType);
|
||||
HttpMultipartParser parser = new HttpMultipartParser(in, contentType);
|
||||
this.multipartData = parser.parse();
|
||||
Logger.debug("HTTP", "Parsed multipart data with " +
|
||||
multipartData.getFiles().size() + " files and " +
|
||||
multipartData.getFields().size() + " fields");
|
||||
} else {
|
||||
if ("chunked".equalsIgnoreCase(headers.get("transfer-encoding"))) {
|
||||
parseChunkedBody(in);
|
||||
return;
|
||||
@@ -131,11 +140,7 @@ public class HttpRequest {
|
||||
}
|
||||
String content = new String(buffer);
|
||||
|
||||
if (contentType.startsWith("multipart/form-data")) {
|
||||
HttpMultipartParser parser = new HttpMultipartParser(in, contentType);
|
||||
this.formData = parser.parse();
|
||||
Logger.debug("HTTP", "Parsed multipart form data: " + formData.size() + " parts");
|
||||
} else if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
||||
if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
||||
this.formData = parseUrlEncodedForm(content);
|
||||
Logger.debug("HTTP", "Parsed URL encoded form data: " + formData.size() + " fields");
|
||||
} else {
|
||||
@@ -143,6 +148,7 @@ public class HttpRequest {
|
||||
Logger.debug("HTTP", "Parsed body with content type: " + contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseChunkedBody(BufferedReader in) throws IOException {
|
||||
StringBuilder content = new StringBuilder();
|
||||
@@ -220,6 +226,10 @@ public class HttpRequest {
|
||||
return new HashMap<>(headers);
|
||||
}
|
||||
|
||||
public HttpMultipartData getMultipartData() {
|
||||
return multipartData;
|
||||
}
|
||||
|
||||
public HttpRequestBody getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ public class Logger {
|
||||
DEBUG, INFO, WARN, ERROR
|
||||
}
|
||||
|
||||
private static final Logger instance = new Logger(LogLevel.INFO);
|
||||
private static final Logger instance = new Logger(LogLevel.DEBUG);
|
||||
|
||||
private LogLevel currentLogLevel;
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.github.lumijiez.example;
|
||||
|
||||
import io.github.lumijiez.core.config.ServerConfig;
|
||||
import io.github.lumijiez.core.http.HttpFileItem;
|
||||
import io.github.lumijiez.core.http.HttpMultipartData;
|
||||
import io.github.lumijiez.core.http.HttpServer;
|
||||
import io.github.lumijiez.core.http.HttpStatus;
|
||||
import io.github.lumijiez.core.ws.WebSocketConnection;
|
||||
@@ -10,6 +12,9 @@ import io.github.lumijiez.example.daos.ProductDao;
|
||||
import io.github.lumijiez.example.models.Product;
|
||||
import io.github.lumijiez.core.logging.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
ProductDao productDao = new ProductDao();
|
||||
@@ -32,6 +37,42 @@ public class Main {
|
||||
res.sendResponse(HttpStatus.OK, "All good, lil bro");
|
||||
});
|
||||
|
||||
server.POST("/upload", (req, res) -> {
|
||||
HttpMultipartData multipartData = req.getMultipartData();
|
||||
|
||||
String description = multipartData.getField("description");
|
||||
String category = multipartData.getField("category");
|
||||
|
||||
HttpFileItem uploadedFile = multipartData.getFile("file");
|
||||
if (uploadedFile != null) {
|
||||
String fileName = uploadedFile.getFileName();
|
||||
String contentType = uploadedFile.getContentType();
|
||||
byte[] fileContent = uploadedFile.getContent();
|
||||
|
||||
File uploadDir = new File("uploads");
|
||||
if (!uploadDir.exists()) {
|
||||
uploadDir.mkdirs();
|
||||
}
|
||||
|
||||
Logger.info("START UPLOAD", fileName);
|
||||
File destination = new File(uploadDir, fileName);
|
||||
uploadedFile.saveTo(destination);
|
||||
Logger.info("DONE UPLOAD", fileName);
|
||||
res.sendResponse(HttpStatus.OK, "Uploaded: " + fileName);
|
||||
// res.sendJson(HttpStatus.OK, Map.of(
|
||||
// "message", "File uploaded successfully",
|
||||
// "fileName", fileName,
|
||||
// "size", fileContent.length,
|
||||
// "description", description
|
||||
// ));
|
||||
Logger.info("START UPLOAD", fileName);
|
||||
} else {
|
||||
res.sendJson(HttpStatus.BAD_REQUEST, Map.of(
|
||||
"error", "No file provided"
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
server.GET("/user", (req, res) -> {
|
||||
Product product = productDao.getProductById(5);
|
||||
res.sendJson(HttpStatus.OK, product);
|
||||
|
||||
Reference in New Issue
Block a user