HTTP 요청 만들기 - CURL 구현하기

김태훈·2023년 6월 28일
0

들어가기 전에

당연히 알고 있겠지만, HTTP 메세지는 그저 텍스트일 뿐입니다. 컴퓨터의 입장에서 HTTP 메세지로 통신한다는 건 텍스트 다발을 보내고 받는 일련의 과정입니다.
톰캣과 같은 웹서버가 해당 텍스트 다발을 HTTP로 인식하고, 규칙에 맞춰 해석할 뿐입니다.
우리가 만드는 CURL은 서버로 요청을 보내고 응답을 받습니다. 현재 단계에서 우리는 HTTP 요청 메세지를 만들건데, 만드는 결과물은 텍스트 다발이라는 사실을 짚고 가도록 하겠습니다.

사전지식

commons-cli 라이브러리에 대해 설명하지 않습니다. 몰라도 이해하는데 지장은 없지만, 사용법 정도를 보고 오면 더 좋습니다.

입출력 양식

예시 1

  • 입력
    -request GET -H accept:*/* -H User-Agent:curl/7.79.1 localhost:8080

  • 출력

    	GET / HTTP/1.1
    	accept: */*
    	host: localhost:8080
    	user-agent: curl/7.79.1

예시 2

  • 입력
    -request GET -H accept:*/* -H User-Agent:curl/7.79.1 www.google.com/search?q=hello&hl=ko

  • 출력

    	GET /search?q=hello&hl=ko HTTP/1.1
    	accept: */*
    	host: www.google.com
    	user-agent: curl/7.79.1

구현

의존성 주입

사용자의 입력값을 파싱할 수 있도록 commons-cli dependency를 추가합니다.
build.gradle


...


dependencies {
	...
    
    // https://mvnrepository.com/artifact/commons-cli/commons-cli
    implementation 'commons-cli:commons-cli:1.5.0'

	...
}

test {
    useJUnitPlatform()
}

입력값 파싱하기

package org.kimtaehoondev;

public class Main {
    public static void main(String[] args) {
        Curl curl = new Curl();
        curl.run(args);
    }
}
package org.kimtaehoondev.domain;

import org.apache.commons.cli.Option;

public enum MyOption {
    HTTP_REQUEST_METHOD("X", "request",
        "HTTP 메서드를 지정하기 위해 사용합니다. 기본값은 GET입니다.",
        true, "command"),
    HEADER("H", "header", "헤더를 추가합니다", true, "line"),
    DATA("d", "data", "데이터를 넣습니다", true, "data");

    private final String optName;
    private final Option option;

    MyOption(String opt, String longOpt, String description, boolean hasArgs, String argName) {
        this.optName = opt;

        Option.Builder builder = Option.builder()
            .option(opt)
            .longOpt(longOpt)
            .desc(description)
            .hasArg(hasArgs);
        if (argName != null) {
            builder.argName(argName);
        }
        this.option = builder.build();
    }

    public Option getOption() {
        return option;
    }

    public String getOptName() {
        return optName;
    }
}
package org.kimtaehoondev.utils;

import java.util.Arrays;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.kimtaehoondev.domain.MyOption;

public class ArgsParser {
    public static CommandLine makeCmdUsingArgs(String[] args) {
        try {
            DefaultParser parser = new DefaultParser();
            Options options = makeOptionsUsingValues();
            return parser.parse(options, args);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    private static Options makeOptionsUsingValues() {
        Options options = new Options();
        Arrays.stream(MyOption.values())
            .map(MyOption::getOption)
            .forEach(options::addOption);

        return options;
    }
}
package org.kimtaehoondev;

import java.util.Arrays;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.kimtaehoondev.domain.HttpRequest;
import org.kimtaehoondev.utils.ArgsParser;
import org.kimtaehoondev.utils.UrlParser;

public class Curl {

    public void run(String[] args) {
        // 입력값을 통해 Http 요청을 만든다
        String url = args[args.length - 1];
        String[] argsExceptUrl = Arrays.copyOfRange(args, 0, args.length - 1);
        CommandLine commandLine = ArgsParser.makeCmdUsingArgs(argsExceptUrl);
        
        // 결과 확인
        System.out.println("url = " + url);
        System.out.println("=========");
        for(Option option : commandLine.getOptions()) {
            System.out.println("option.getOpt() = " + option.getOpt());
            System.out.println("option.getValue() = " + option.getValue());
        }

지금까지 로직을 실행하면 아래와 같은 결과가 나옵니다.

사용자가 입력한 Arguments를 사용해 CommandLine 객체를 만들어내는데, ArgsParser라는 유틸성 객체를 만들어 해당 역할을 하도록 만들어줬습니다.
또 우리가 사용하는 플래그는 세 가지로 고정되어 있기 때문에, MyOption이라는 enum 클래스를 만들어 사용하고 있습니다.

앞서 언급했던 것처럼 commons-cli 라이브러리 사용법에 대한 설명은 하지 않겠습니다. 여기까지는 잘 모르셔도 그냥 복사해서 사용하셔도 무방합니다. 이후 과정을 이해하는 데 지장 없습니다.

HttpRequest 객체 만들기

package org.kimtaehoondev.utils;

import java.net.MalformedURLException;
import java.net.URL;

public class UrlParser {
    public static URL parse(String url) {
        try {
            if (!(url.startsWith("http://") || (url.startsWith("https://")))) {
                url = "http://" + url;
            }
            URL temp = new URL(url);
            if (temp.getPort() != -1) {
                return temp;
            }
            if (temp.getProtocol().equals("http")) {
                return new URL(temp.getProtocol(), temp.getHost(), 80, temp.getFile());
            }
            if (temp.getProtocol().equals("https")) {
                return new URL(temp.getProtocol(), temp.getHost(), 443, temp.getFile());
            }
            throw new RuntimeException("지원하지 않는 프로토콜입니다");
        } catch (MalformedURLException e) {
            throw new RuntimeException("URL을 파싱할 수 없습니다");
        }
    }
}
package org.kimtaehoondev.domain;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class Header {
    private static final int KEY_VALUE_SIZE = 2;
    private static final int KEY = 0;
    private static final int VALUE = 1;
    private static final String DELIMITER = ":";

    private final HeaderName key;
    private final String value;

    public Header(String data) {
        List<String> keyAndValue = Arrays.stream(data.split(DELIMITER, KEY_VALUE_SIZE))
            .map(String::trim)
            .collect(Collectors.toList());
        this.key = new HeaderName(keyAndValue.get(KEY));
        this.value = keyAndValue.get(VALUE);
    }

    public Header(HeaderName key, String value) {
        this.key = key;
        this.value = value;
    }

    public HeaderName getKey() {
        return key;
    }

    public String getValue() {
        return value;
    }

    public boolean isKeyEquals(String key) {
        return this.key.equals(new HeaderName(key));
    }

    public boolean isValueEqual(String value) {
        return this.value.equals(value.toLowerCase().trim());
    }

    public String getPrettier() {
        return this.key.getValue() + ": " + this.value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Header header = (Header) o;
        return Objects.equals(key, header.key) &&
            Objects.equals(value, header.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(key, value);
    }
}
package org.kimtaehoondev.domain;

import java.util.Objects;

public class HeaderName {
    public static final HeaderName HOST = new HeaderName("host");

    private final String value;

    public HeaderName(String value) {
        this.value = value.toLowerCase().trim();
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        HeaderName that = (HeaderName) o;
        return Objects.equals(value, that.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}
package org.kimtaehoondev.domain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Headers {
    private final Map<HeaderName, List<Header>> store = new HashMap<>();

    public void put(HeaderName key, String value) {
        if (!store.containsKey(key)) {
            List<Header> headers = new ArrayList<>();
            store.put(key, headers);
        }
        List<Header> headers = store.get(key);
        headers.add(new Header(key, value));
    }

    public void put(Header header) {
        put(header.getKey(), header.getValue());
    }

    public boolean containsKey(HeaderName key) {
        return store.containsKey(key);
    }

    public List<Header> getAll() {
        List<Header> result = new ArrayList<>();
        for (List<Header> header : store.values()) {
            result.addAll(header);
        }
        return result;
    }

    public List<Header> get(HeaderName key) {
        return store.get(key);
    }
}
package org.kimtaehoondev.domain;

import java.util.Arrays;
import java.util.Objects;

public enum HttpMethod {
    GET, POST, PUT, PATCH, DELETE;

    public static HttpMethod find(String value) {
        return Arrays.stream(HttpMethod.values())
            .filter(each -> Objects.equals(each.name(), value.toUpperCase()))
            .findAny()
            .orElseThrow(() -> new RuntimeException("존재하지 않는 HTTP REQUEST"));
    }
}
package org.kimtaehoondev.domain;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.cli.Option;

public class HttpRequest {
    private static final String EMPTY_LINE = "";
    private static final HttpMethod DEFAULT_HTTP_METHOD = HttpMethod.GET;
    private static final String DEFAULT_HTTP_VERSION = "HTTP/1.1";

    private String requestTarget;
    private HttpMethod httpMethod;
    private String httpVersion;
    private final Headers headers;
    private String body;


    public HttpRequest(URL url) {
        String requestTarget = url.getFile();
        if (requestTarget.isBlank()) {
            requestTarget = "/";
        }

        this.requestTarget = requestTarget;
        this.httpMethod = DEFAULT_HTTP_METHOD;
        this.httpVersion = DEFAULT_HTTP_VERSION;
        this.headers = new Headers();
        String host = url.getHost();
        if (url.getPort() != -1) {
            host += (":" + url.getPort());
        }
        this.headers.put(HeaderName.HOST, host);
    }

    public void setValueUsingParams(Option option) {
        String optionValue = option.getValue();

        if (Objects.equals(option.getOpt(), MyOption.HTTP_REQUEST_METHOD.getOptName())) {
            this.httpMethod = HttpMethod.find(optionValue);
            return;
        }
        if (Objects.equals(option.getOpt(), MyOption.HEADER.getOptName())) {
            addHeader(new Header(optionValue));
            return;
        }
        if (Objects.equals(option.getOpt(), MyOption.DATA.getOptName())) {
            setBody(optionValue);
            return;
        }
        throw new RuntimeException("선택할 수 없는 Option입니다");
    }

    private void setBody(String optionValue) {
        StringJoiner sj = new StringJoiner(",");
        String[] params = optionValue.split("&");
        for (String param : params) {
            String[] keyAndValue = param.split("=");
            sj.add("\"" + keyAndValue[0] + "\"" + ":" + "\"" + keyAndValue[1] + "\"");
        }

        this.body = "{" + sj + "}";
        Header contentTypeHeader = new Header(new HeaderName("Content-Type"), "application/json");
        addHeader(contentTypeHeader);
        Header contentLengthHeader = new Header(new HeaderName("Content-Length"),
            String.valueOf(this.body.getBytes(StandardCharsets.UTF_8).length));
        addHeader(contentLengthHeader);
    }
    
    private void addHeader(Header header) {
        headers.put(header.getKey(), header.getValue());
    }

변경된 CURL

package org.kimtaehoondev;

import java.util.Arrays;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.kimtaehoondev.domain.HttpRequest;
import org.kimtaehoondev.utils.ArgsParser;
import org.kimtaehoondev.utils.UrlParser;

public class Curl {

    public void run(String[] args) {
        // 입력값을 통해 Http 요청을 만든다
        String url = args[args.length - 1];
        String[] argsExceptUrl = Arrays.copyOfRange(args, 0, args.length - 1);
        CommandLine commandLine = ArgsParser.makeCmdUsingArgs(argsExceptUrl);
        
        // 결과를 확인하는 출력문을 삭제하고, 아래 로직이 추가됨
        HttpRequest httpRequest = new HttpRequest(UrlParser.parse(url));

        }

UrlParser

사용자가 입력한 String 타입의 url을 URL 타입으로 바꿔주는 역할을 합니다. 유틸성 클래스로 만들어줬습니다.

Header, HeaderName, Headers

해당 객체를 설명하기 이전, Http에서의 Header에 대해 간단한 설명을 하겠습니다. Header는 Key와 Value로 이루어져있고, Key는 중복이 가능합니다. 또, Key는 대소문자를 구별하지 않는다는 특징을 갖습니다.
위 세개의 객체는 해당 요구사항을 충족시키기 위해 사용합니다.

Header 클래스는 key와 value를 필드로 갖습니다. key의 타입은 HeaderName입니다.
HeaderName 클래스는 대소문자를 구별하지 않는다는 요구사항을 지키기 위해 만든 클래스입니다. 해당 객체는 생성되는 시점에 toLowerCase 메서드를 사용해 내부값을 소문자로 변환합니다.
Headers 클래스는 내부 필드로 Map<HeaderName, List<Header>> store 을 갖습니다.
중복되는 헤더 이름을 가질 수 있기 때문에 위와 같은 구조를 선택했습니다. 사용의 편의성을 위해 Headers라는 일급콜렉션 클래스로 만들어 사용하고 있습니다.

HttpRequest

위 사진은 Http Request Message의 스펙입니다. Start Line, Header, Message Body라는 정보를 가지고 있는데, Http Request Message는 Http Method,RequestTarget, Http Version 이라는 정보가 필요합니다.

HttpRequest 클래스에 해당 정보들을 필드로 추가합니다. 해당 정보들은 URL을 파싱하고, 사용자가 입력한 Arguments로 만든 Option을 통해서 값을 넣어줍니다.

HttpRequestMessage 만들기

HttpRequest 객체를 사용해 실제 HttpRequestMessage를 만들어 보겠습니다. HttpRequest 객체에 serialize 라는 메서드를 추가합니다.

public class HttpRequest {
	// 이전 로직들...
    
    /**
     * 해당 객체를 List<String>으로 직렬화합니다.
     * 서버로 Http 요청을 보내기 위해 사용합니다
     */
    public List<String> serialize() {
        List<String> result = new ArrayList<>();
        String startLine = httpMethod + " " + requestTarget + " " + httpVersion;
        result.add(startLine);

        List<String> total = headers.getAll().stream()
            .map(Header::getPrettier)
            .collect(Collectors.toList());
        result.addAll(total);
        result.add(EMPTY_LINE);

        if (body != null) {
            result.add(body);
        }
        return result;
    }
}

Http Message는 Start Line, Header, 공백 라인, Message Body 순서로 구성됩니다.
먼저 httpMethod, requestTarget, httpVersion을 사용해 StartLine을 만들어 List<String> result 에 추가합니다.
이후 Header들을 Key: Value 형태로 만들어 result에 추가합니다.
이후, 공백 라인을 하나 가져야 하기 때문에 EMPTY_LINE을 추가해줍니다.
마지막으로 Body 정보가 있으면 Body를 넣어주고 result 를 반환합니다.

그리고 CURL을 아래와 같이 변경합니다.

package org.kimtaehoondev;

import java.util.Arrays;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.kimtaehoondev.domain.HttpRequest;
import org.kimtaehoondev.utils.ArgsParser;
import org.kimtaehoondev.utils.UrlParser;

public class Curl {

    public void run(String[] args) {
        // 입력값을 통해 Http 요청을 만든다
        String url = args[args.length - 1];
        String[] argsExceptUrl = Arrays.copyOfRange(args, 0, args.length - 1);
        CommandLine commandLine = ArgsParser.makeCmdUsingArgs(argsExceptUrl);

        HttpRequest httpRequest = new HttpRequest(UrlParser.parse(url));
        for(Option option : commandLine.getOptions()) {
            httpRequest.setValueUsingParams(option);
        }
        List<String> result = httpRequest.serialize();

        // 결과확인
        System.out.println("=====HTTP Request Message START");
        for (String each : result) {
            System.out.println(each);
        }
        System.out.println("=====HTTP Request Message END");

        // 특정 서버로 해당 요청을 보낸다
        // 해당 서버에서 받은 응답을 Http로 만든다
    }

}

여기까지 하면 알맞은 HttpRequestMessage가 만들어진 걸 확인할 수 있습니다.

결과 확인

예시 1

  • 입력
    -request GET -H accept:*/* -H User-Agent:curl/7.79.1 localhost:8080

  • (실제 CURL 명령어 사용 시 만들어지는 Http Request Message)

  • 결과

예시 2

  • 입력
    -request GET -H accept:*/* -H User-Agent:curl/7.79.1 www.google.com/search?q=hello&hl=ko

  • 출력 예시(존재하지 않는 URL이라 실제 curl 프로그램으로 확인이 불가)

    	GET /search?q=hello&hl=ko HTTP/1.1
    	accept: */*
    	host: www.google.com
    	user-agent: curl/7.79.1
  • 결과

To Be...

다음 글에서는 Http Request Message를 해당되는 서버에 직접 보내는 과정을 구현해보겠습니다.

현재까지 로직

혹시 중간에 놓친 분들을 위해 지금까지 어플리케이션 코드를 올려두겠습니다.

package org.kimtaehoondev.domain;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

public class Header {
    private static final int KEY_VALUE_SIZE = 2;
    private static final int KEY = 0;
    private static final int VALUE = 1;
    private static final String DELIMITER = ":";

    private final HeaderName key;
    private final String value;

    public Header(String data) {
        List<String> keyAndValue = Arrays.stream(data.split(DELIMITER, KEY_VALUE_SIZE))
            .map(String::trim)
            .collect(Collectors.toList());
        this.key = new HeaderName(keyAndValue.get(KEY));
        this.value = keyAndValue.get(VALUE);
    }

    public Header(HeaderName key, String value) {
        this.key = key;
        this.value = value;
    }

    public HeaderName getKey() {
        return key;
    }

    public String getValue() {
        return value;
    }

    public boolean isKeyEquals(String key) {
        return this.key.equals(new HeaderName(key));
    }

    public boolean isValueEqual(String value) {
        return this.value.equals(value.toLowerCase().trim());
    }

    public String getPrettier() {
        return this.key.getValue() + ": " + this.value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Header header = (Header) o;
        return Objects.equals(key, header.key) &&
            Objects.equals(value, header.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(key, value);
    }
}
package org.kimtaehoondev.domain;

import java.util.Objects;

public class HeaderName {
    public static final HeaderName HOST = new HeaderName("host");

    private final String value;

    public HeaderName(String value) {
        this.value = value.toLowerCase().trim();
    }

    public String getValue() {
        return value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        HeaderName that = (HeaderName) o;
        return Objects.equals(value, that.value);
    }

    @Override
    public int hashCode() {
        return Objects.hash(value);
    }
}
package org.kimtaehoondev.domain;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Headers {
    private final Map<HeaderName, List<Header>> store = new HashMap<>();

    public void put(HeaderName key, String value) {
        if (!store.containsKey(key)) {
            List<Header> headers = new ArrayList<>();
            store.put(key, headers);
        }
        List<Header> headers = store.get(key);
        headers.add(new Header(key, value));
    }

    public void put(Header header) {
        put(header.getKey(), header.getValue());
    }

    public boolean containsKey(HeaderName key) {
        return store.containsKey(key);
    }

    public List<Header> getAll() {
        List<Header> result = new ArrayList<>();
        for (List<Header> header : store.values()) {
            result.addAll(header);
        }
        return result;
    }

    public List<Header> get(HeaderName key) {
        return store.get(key);
    }
}
package org.kimtaehoondev.domain;

import java.util.Arrays;
import java.util.Objects;

public enum HttpMethod {
    GET, POST, PUT, PATCH, DELETE;

    public static HttpMethod find(String value) {
        return Arrays.stream(HttpMethod.values())
            .filter(each -> Objects.equals(each.name(), value.toUpperCase()))
            .findAny()
            .orElseThrow(() -> new RuntimeException("존재하지 않는 HTTP REQUEST"));
    }
}
package org.kimtaehoondev.domain;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.apache.commons.cli.Option;

public class HttpRequest {
    private static final String EMPTY_LINE = "";
    private static final HttpMethod DEFAULT_HTTP_METHOD = HttpMethod.GET;
    private static final String DEFAULT_HTTP_VERSION = "HTTP/1.1";

    private String requestTarget;
    private HttpMethod httpMethod;
    private String httpVersion;
    private final Headers headers;
    private String body;


    public HttpRequest(URL url) {
        String requestTarget = url.getFile();
        if (requestTarget.isBlank()) {
            requestTarget = "/";
        }

        this.requestTarget = requestTarget;
        this.httpMethod = DEFAULT_HTTP_METHOD;
        this.httpVersion = DEFAULT_HTTP_VERSION;
        this.headers = new Headers();
        String host = url.getHost();
        if (url.getPort() != -1) {
            host += (":" + url.getPort());
        }
        this.headers.put(HeaderName.HOST, host);
    }

    public void setValueUsingParams(Option option) {
        String optionValue = option.getValue();

        if (Objects.equals(option.getOpt(), MyOption.HTTP_REQUEST_METHOD.getOptName())) {
            this.httpMethod = HttpMethod.find(optionValue);
            return;
        }
        if (Objects.equals(option.getOpt(), MyOption.HEADER.getOptName())) {
            addHeader(new Header(optionValue));
            return;
        }
        if (Objects.equals(option.getOpt(), MyOption.DATA.getOptName())) {
            setBody(optionValue);
            return;
        }
        throw new RuntimeException("선택할 수 없는 Option입니다");
    }

    private void setBody(String optionValue) {
        StringJoiner sj = new StringJoiner(",");
        String[] params = optionValue.split("&");
        for (String param : params) {
            String[] keyAndValue = param.split("=");
            sj.add("\"" + keyAndValue[0] + "\"" + ":" + "\"" + keyAndValue[1] + "\"");
        }

        this.body = "{" + sj + "}";
        Header contentTypeHeader = new Header(new HeaderName("Content-Type"), "application/json");
        addHeader(contentTypeHeader);
        Header contentLengthHeader = new Header(new HeaderName("Content-Length"),
            String.valueOf(this.body.getBytes(StandardCharsets.UTF_8).length));
        addHeader(contentLengthHeader);
    }
    
    private void addHeader(Header header) {
        headers.put(header.getKey(), header.getValue());
    }
package org.kimtaehoondev.domain;

import org.apache.commons.cli.Option;

public enum MyOption {
    HTTP_REQUEST_METHOD("X", "request",
        "HTTP 메서드를 지정하기 위해 사용합니다. 기본값은 GET입니다.",
        true, "command"),
    HEADER("H", "header", "헤더를 추가합니다", true, "line"),
    DATA("d", "data", "데이터를 넣습니다", true, "data");

    private final String optName;
    private final Option option;

    MyOption(String opt, String longOpt, String description, boolean hasArgs, String argName) {
        this.optName = opt;

        Option.Builder builder = Option.builder()
            .option(opt)
            .longOpt(longOpt)
            .desc(description)
            .hasArg(hasArgs);
        if (argName != null) {
            builder.argName(argName);
        }
        this.option = builder.build();
    }

    public Option getOption() {
        return option;
    }

    public String getOptName() {
        return optName;
    }
}
package org.kimtaehoondev.utils;

import java.util.Arrays;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.kimtaehoondev.domain.MyOption;

public class ArgsParser {
    public static CommandLine makeCmdUsingArgs(String[] args) {
        try {
            DefaultParser parser = new DefaultParser();
            Options options = makeOptionsUsingValues();
            return parser.parse(options, args);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    private static Options makeOptionsUsingValues() {
        Options options = new Options();
        Arrays.stream(MyOption.values())
            .map(MyOption::getOption)
            .forEach(options::addOption);

        return options;
    }
}
package org.kimtaehoondev.utils;

import java.net.MalformedURLException;
import java.net.URL;

public class UrlParser {
    public static URL parse(String url) {
        try {
            if (!(url.startsWith("http://") || (url.startsWith("https://")))) {
                url = "http://" + url;
            }
            URL temp = new URL(url);
            if (temp.getPort() != -1) {
                return temp;
            }
            if (temp.getProtocol().equals("http")) {
                return new URL(temp.getProtocol(), temp.getHost(), 80, temp.getFile());
            }
            if (temp.getProtocol().equals("https")) {
                return new URL(temp.getProtocol(), temp.getHost(), 443, temp.getFile());
            }
            throw new RuntimeException("지원하지 않는 프로토콜입니다");
        } catch (MalformedURLException e) {
            throw new RuntimeException("URL을 파싱할 수 없습니다");
        }
    }
}
package org.kimtaehoondev;

import java.util.Arrays;
import java.util.List;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.kimtaehoondev.domain.HttpRequest;
import org.kimtaehoondev.utils.ArgsParser;
import org.kimtaehoondev.utils.UrlParser;

public class Curl {

    public void run(String[] args) {
        // 입력값을 통해 Http 요청을 만든다
        String url = args[args.length - 1];
        String[] argsExceptUrl = Arrays.copyOfRange(args, 0, args.length - 1);
        CommandLine commandLine = ArgsParser.makeCmdUsingArgs(argsExceptUrl);

        HttpRequest httpRequest = new HttpRequest(UrlParser.parse(url));
        for(Option option : commandLine.getOptions()) {
            httpRequest.setValueUsingParams(option);
        }
        List<String> result = httpRequest.serialize();

        // 결과확인
        System.out.println("=====HTTP Request Message START");
        for (String each : result) {
            System.out.println(each);
        }
        System.out.println("=====HTTP Request Message END");

        // 특정 서버로 해당 요청을 보낸다
        // 해당 서버에서 받은 응답을 Http로 만든다
    }

}
package org.kimtaehoondev;

public class Main {
    public static void main(String[] args) {
        Curl curl = new Curl();
        curl.run(args);
    }
}
 // Build.gradle 파일입니다. 
 // dependencies에 아래 commons-cli를 추가합니다.
 ...
 
dependencies {

    // https://mvnrepository.com/artifact/commons-cli/commons-cli
    implementation 'commons-cli:commons-cli:1.5.0'
}

...
profile
작은 지식 모아모아

0개의 댓글