Skip to content

Commit

Permalink
Merge pull request #1 from eckrin/week3/feature1
Browse files Browse the repository at this point in the history
remote 커밋내역 머지
  • Loading branch information
eckrin authored Jul 24, 2024
2 parents 65b3be0 + c82d120 commit 3d30e23
Show file tree
Hide file tree
Showing 55 changed files with 2,534 additions and 210 deletions.
13 changes: 8 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ fabric.properties
# Editor-based Rest Client
.idea/httpRequests

# Android studio 3.1+ serialized cache file
# Android studio 3.1+ serialized cache viewFile
.idea/caches/build_file_checksums.ser

### Intellij Patch ###
Expand All @@ -102,7 +102,7 @@ fabric.properties
.idea/**/markdown-navigator-enh.xml
.idea/**/markdown-navigator/

# Cache file creation bug
# Cache viewFile creation bug
# See https://youtrack.jetbrains.com/issue/JBR-2257
.idea/$CACHE_FILE$

Expand All @@ -115,10 +115,10 @@ fabric.properties
.idea/**/azureSettings.xml

### Java ###
# Compiled class file
# Compiled class viewFile
*.class

# Log file
# Log viewFile
*.log

# BlueJ files
Expand All @@ -136,6 +136,9 @@ fabric.properties
*.tar.gz
*.rar

# eckrin
src/**/eckrin/

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*
Expand Down Expand Up @@ -181,7 +184,7 @@ Temporary Items
# Ignore Gradle GUI config
gradle-app.setting

# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
# Avoid ignoring Gradle wrapper jar viewFile (.jar files are usually ignored)
!gradle-wrapper.jar

# Avoid ignore Gradle wrappper properties
Expand Down
10 changes: 10 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dependencies {
implementation 'ch.qos.logback:logback-classic:1.2.3'
testImplementation 'org.assertj:assertj-core:3.16.1'

// H2
implementation 'com.h2database:h2:2.1.214'

}

Expand Down
2 changes: 1 addition & 1 deletion gradlew.bat
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem you may not use this viewFile except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
Expand Down
49 changes: 49 additions & 0 deletions src/main/java/common/FileUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package common;

import file.ViewFile;
import web.HttpRequest;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.UUID;

/**
* 파일 저장, 확장자 추출, 파일 생성 등을 담당하는 유틸리티 클래스
*/
public class FileUtils {

public static final String STATIC_DIR_PATH = "./src/main/resources/static";
public static final String MY_ARTICLE_PATH = "/eckrin/";

public static String getStaticFilePath(String path) {
return STATIC_DIR_PATH+path;
}

public static String getExtensionFromPath(String path) {
return path.split("\\.")[1];
}

public static String getImageExtensionFromPath(String body) {
String[] bodySplit = body.split("image/");
if(bodySplit.length>=2) return bodySplit[1];
else return null;
}

public static ViewFile makeFileFromRequest(HttpRequest request) {
String filePath = request.getPath();
String extension = FileUtils.getExtensionFromPath(filePath);

return new ViewFile(filePath, extension);
}

public static String saveFile(byte[] data, String extension) {
String fileName = UUID.randomUUID()+"."+extension;
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(STATIC_DIR_PATH+ MY_ARTICLE_PATH+fileName))) {
bos.write(data);
} catch (IOException e) {
e.printStackTrace();
}
return fileName;
}
}
27 changes: 27 additions & 0 deletions src/main/java/common/JsonBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package common;

import model.User;

import java.util.Collection;

/**
* User 리스트를 Json으로 반환하기 위한 빌더 클래스
*/
public class JsonBuilder {

public static String buildJsonResponse(Collection<User> users) {
StringBuilder jsonResponse = new StringBuilder("[");
for (User user : users) {
jsonResponse.append("{")
.append("\"userId\":\"").append(user.getUserId()).append("\",")
.append("\"name\":\"").append(user.getName()).append("\",")
.append("\"password\":\"").append(user.getPassword()).append("\"")
.append("},");
}
if (jsonResponse.length() > 1) {
jsonResponse.setLength(jsonResponse.length() - 1); // 마지막 쉼표 제거
}
jsonResponse.append("]");
return jsonResponse.toString();
}
}
161 changes: 161 additions & 0 deletions src/main/java/common/RequestUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.HeaderKey;
import web.HttpMethod;
import web.HttpRequest;
import web.MIME;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
* Request를 파싱하여 Request Line, Header, Body를 추출하는 클래스
* Header와 Body는 \r\n\r\n으로 구분하며, 헤더의 Content-Length값에 따라서 Body를 읽는다.
*/
public class RequestUtils {

private static final Logger logger = LoggerFactory.getLogger(RequestUtils.class);

/**
* InputStream에서 Request를 String형태로 읽어온 후, HttpRequest를 만들어 반환한다.
* body는 byte[]형태로 받아온다.
*/
public static HttpRequest parseHttpRequest(InputStream in) throws IOException {
final int TMP_BUFFER_SIZE = 1024;

int ch;
StringBuffer sb = new StringBuffer();
int contentLength = 0;
byte[] body = null;

// Request Line부터 Body 전까지 읽는다
// Request Line과 Body 사이에는 '\r\n'이 두번 오게 된다.
boolean lastWasCR = false;
boolean lastWasLF = false;
while((ch = in.read())!=-1) {
sb.append((char) ch);

if(ch=='\r') {
lastWasCR = true;
} else if (ch=='\n') {
if(lastWasCR && lastWasLF) {
break;
}
lastWasLF = true;
} else {
lastWasCR = false;
lastWasLF = false;
}
}

// Content-Length 헤더 찾기
String header = sb.toString();
String[] headerLines = header.split("\r\n");
for(String line: headerLines) {
if(line.toLowerCase().startsWith(HeaderKey.CONTENT_LENGTH.getKey())) {
contentLength = Integer.parseInt(line.split(":")[1].trim());
break;
}
}
logger.debug("{}", sb);

StringBuffer sbb = new StringBuffer();
// Content-Length가 0보다 크다면 body까지 읽어서 로그에 출력한다.
if (contentLength > 0) {
body = new byte[contentLength];
int bytesRead = 0;
int offset = 0;

while (bytesRead < contentLength) {
int bytesToRead = Math.min(TMP_BUFFER_SIZE, contentLength - bytesRead);
int readSize = in.read(body, offset, bytesToRead);

if (readSize == -1) {
throw new IOException("Unexpected end of input stream while reading body");
}

bytesRead += readSize;
offset += readSize;
}

sbb.append(new String(body)).append("\n");
}

// logger.debug("{}", sbb);

return parseRequest(sb.toString(), body);
}

/**
* request로 들어온 HTTP 요청을 한줄씩 파싱하여 적절한 HttpRequest 객체를 생성
* @param request Request Line과 Header
* @param body request body (byte[])
* @return HttpRequest
*/
private static HttpRequest parseRequest(String request, byte[] body) {
HttpMethod method;
String path, contentType = MIME.UNKNOWN.getType();
LinkedList<String> accept = new LinkedList<>();
int contentLength = 0;
Map<String, String> cookie = new HashMap<>();

String[] requestLine = request.split("\n");

// Request Line
String[] line_1 = requestLine[0].split(" ");
method = HttpMethod.valueOf(line_1[0]);
path = line_1[1];

// Header
for(int i=1; i<requestLine.length; i++) {
if(requestLine[i].split(":").length==1) continue;
String[] line_N = requestLine[i].split(":");
String key = line_N[0].trim();
String value = line_N[1].trim();

// Accept헤더 MimeType 설정
if(key.equalsIgnoreCase(HeaderKey.ACCEPT.getKey())) {
String[] acceptLine = value.split(";");
String[] mimeType = acceptLine[0].split(",");
accept.addAll(Arrays.asList(mimeType));
} else if(key.equalsIgnoreCase(HeaderKey.CONTENT_LENGTH.getKey())) {
contentLength = Integer.parseInt(value);
} else if(key.equalsIgnoreCase(HeaderKey.CONTENT_TYPE.getKey())) {
contentType = value;
} else if(key.equalsIgnoreCase(HeaderKey.COOKIE.getKey())) {
String[] cookies = value.split(";");
for(String c: cookies) {
String cookieName = c.split("=")[0].trim();
String cookieId = c.split("=")[1].trim();
cookie.put(cookieName, cookieId);
}
}
}

return new HttpRequest.HttpRequestBuilder()
.method(method)
.path(path)
.accept(accept)
.contentLength(contentLength)
.contentType(contentType)
.cookie(cookie)
.body(body)
.build();
}

/**
* Multipart 파일의 Body 구분자를 추출하기 위한 메서드
* @param contentType Request 헤더의 Content-Type
* @return Body Delimiter
*/
public static String getBoundaryKey(String contentType) {
return contentType.split("boundary=")[1];
}
}

Loading

0 comments on commit 3d30e23

Please sign in to comment.