Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ dependencies {
implementation 'ch.qos.logback:logback-classic:1.2.3'
testImplementation 'org.assertj:assertj-core:3.16.1'

testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation('org.mockito:mockito-junit-jupiter:4.11.0') {
exclude group: 'org.junit.jupiter'
exclude group: 'org.junit.platform'
}


}

Expand Down
6 changes: 4 additions & 2 deletions src/main/java/webserver/http/HttpServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public class HttpServlet implements Runnable{
private final Socket connection;
private final HttpRequestConverter requestConverter;
private final HttpResponseConverter responseConverter;
private ExceptionHandlerMapping handlerMapping;
private WasServlet servlet;
private final ExceptionHandlerMapping handlerMapping;
private final WasServlet servlet;

public HttpServlet(WasServlet servlet,
ExceptionHandlerMapping handlerMapping,
Expand All @@ -37,6 +37,8 @@ public void run() {
responseConverter.sendResponse(response, connection);
} catch (Exception e){
handlerMapping.handle(e, connection);
} finally {
try { connection.close(); } catch (Exception ignore) {}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public class HttpBufferedReaderRequestConverter implements HttpRequestConverter
private static final Logger logger = LoggerFactory.getLogger(HttpBufferedReaderRequestConverter.class);
public HttpRequest parseRequest(Socket connection){

try (InputStream in = connection.getInputStream()) {
try {
InputStream in = connection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
BufferedReader bufferedReader = new BufferedReader(reader);
String firstLine = bufferedReader.readLine();
Expand All @@ -24,10 +25,12 @@ public HttpRequest parseRequest(Socket connection){
int idx = line.indexOf(':');
//TODO: idx == -1 일 경우 Throw Exception

request.setHeader(line.substring(0, idx), line.substring(idx));
request.setHeader(line.substring(0, idx).strip(), line.substring(idx+1).strip());
logger.debug("New Header Added:{} - {}", line.substring(0, idx), line.substring(idx));
}

//TODO: Body 파싱 추가

return request;

} catch (IOException e) {
Expand Down
12 changes: 5 additions & 7 deletions src/main/java/webserver/http/request/HttpRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@
import java.util.Map;

public class HttpRequest {
private HttpMethod method;
private final HttpMethod method;
private final Map<String, String> headers;
private final URI uri;
private String httpVersion;
private String contentType;
private URI uri;

private Map<String, String> headers;
private Map<String, String> queryMap;

private byte[] body;
private InetSocketAddress requestAddress;

public String getHeader(String key){
return headers.get(key);
return headers.get(key.toLowerCase());
}
public void setHeader(String key, String value){
headers.put(key, value);
headers.put(key.toLowerCase(), value);
}

public List<String> getHeaders(){
Expand All @@ -52,8 +52,6 @@ public HttpMethod getMethod(){
return this.method;
}


private HttpRequest (){}
private HttpRequest (HttpMethod method,
String target,
String httpVersion) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package webserver.http.response;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class HttpBufferedStreamResponseConverter implements HttpResponseConverter {

@Override
public boolean support() {
return true;
}

@Override
public void sendResponse(HttpResponse response, Socket connection) {
try {
OutputStream raw = connection.getOutputStream();
BufferedOutputStream out = new BufferedOutputStream(raw);

byte[] body = response.getBody();
if (body == null) body = new byte[0];

String statusLine =
"HTTP/1.1 " + response.getStatus().getCode() + " " + response.getStatus() + "\r\n";
out.write(statusLine.getBytes(StandardCharsets.ISO_8859_1));

for (String key : response.getHeaders()) {
List<String> values = response.getHeader(key);
for (String value : values) {
String headerLine = key + ": " + value + "\r\n";
out.write(headerLine.getBytes(StandardCharsets.ISO_8859_1));
}
}

out.write("\r\n".getBytes(StandardCharsets.ISO_8859_1));
if (body.length > 0) {
out.write(body);
}
out.flush();

} catch (IOException e) {
throw new IllegalStateException("Failed to send HTTP response", e);
}
}
}
20 changes: 13 additions & 7 deletions src/main/java/webserver/http/response/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,24 @@

import webserver.http.HttpStatus;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HttpResponse {
private final HttpStatus status;
private Map<String, List<String>> headers;
private final Map<String, List<String>> headers;
private byte[] body;

private HttpResponse (HttpStatus status){
this.status = status;
this.headers = new HashMap<>();
setHeader("Date", DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()));
setHeader("Server", "be-was");
setHeader("Connection", "close");
}

public static HttpResponse of (HttpStatus status){
Expand All @@ -30,19 +35,19 @@ public List<String> getHeaders() {
}

public List<String> getHeader(String key){
List<String> values = headers.get(key);
List<String> values = headers.get(key.toLowerCase());
return values != null ? values : new ArrayList<>();
}

public void addHeader(String key, String value){
if(!headers.containsKey(key))
headers.put(key, new ArrayList<>());
headers.get(key).add(value);
if(!headers.containsKey(key.toLowerCase()))
headers.put(key.toLowerCase(), new ArrayList<>());
headers.get(key.toLowerCase()).add(value);
}

public void setHeader(String key, String value){
headers.put(key, new ArrayList<>());
headers.get(key).add(value);
headers.put(key.toLowerCase(), new ArrayList<>());
headers.get(key.toLowerCase()).add(value);
}

public byte[] getBody() {
Expand All @@ -51,5 +56,6 @@ public byte[] getBody() {

public void setBody(byte[] body) {
this.body = body;
setHeader("Content-Length", String.valueOf(body.length));
}
}
38 changes: 38 additions & 0 deletions src/test/java/webserver/http/request/HttpRequestConverterTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package webserver.http.request;

import org.junit.jupiter.api.Test;

import java.net.Socket;

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

class HttpRequestConverterTest {
private final HttpRequestConverter converter = new HttpBufferedReaderRequestConverter();

@Test
void parse_GET_request_test() throws Exception {
String raw =
"GET /hello?name=ta&x=1 HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"User-Agent: junit\r\n" +
"\r\n";

Socket socket = mock(Socket.class);
when(socket.getInputStream()).thenReturn(
new ByteArrayInputStream(raw.getBytes(StandardCharsets.ISO_8859_1))
);

HttpRequest req = converter.parseRequest(socket);

assertThat(req.getMethod().name()).isEqualTo("GET");
assertThat(req.getPath()).isEqualTo("/hello");
assertThat(req.getQueryValue("name")).isEqualTo("ta");
assertThat(req.getQueryValue("x")).isEqualTo("1");
assertThat(req.getHeader("Host")).isEqualTo("localhost");
assertThat(req.getHeader("User-Agent")).isEqualTo("junit");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package webserver.http.response;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import webserver.http.HttpStatus;

import java.io.ByteArrayOutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;


@ExtendWith(MockitoExtension.class)
class HttpResponseConverterTest {
private final HttpResponseConverter converter = new HttpBufferedStreamResponseConverter();

@Test
void write_response_to_socket_test() throws Exception {
Socket socket = mock(Socket.class);
ByteArrayOutputStream out = new ByteArrayOutputStream();
when(socket.getOutputStream()).thenReturn(out);

HttpResponse res = HttpResponse.of(HttpStatus.OK);
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.setBody("PONG".getBytes(StandardCharsets.UTF_8));

converter.sendResponse(res, socket);

String raw = out.toString(StandardCharsets.ISO_8859_1);

assertThat(raw).startsWith("HTTP/1.1 200");
assertThat(raw).contains("\r\n\r\n");
assertThat(raw).containsIgnoringCase("Content-Length: 4");
assertThat(raw).endsWith("PONG");
}

}
Loading