본문 바로가기
프로그래밍/Java-자주쓰는예제

【Java-데이터 통신】Postman 처럼 HTTP 통신하기 - 1.개요

by 코이킹 2021. 9. 20.
반응형

1. 설명 

이 포스트에서는 Java의 HttpURLConnection을 사용하여 "HTTP Request를 보내고 Reponse를 처리"( ※ 이하

HTTP요청 흐름으로 표기) 하는 예제를 구현하기 위해,
모듈에 대한 개요를 기술하겠습니다. 


1) 왜 HttpURLConnection을 사용해서 구현하는 지

제가 일하고 있는 곳에서는 1.6, 8, 11등 다양한 JDK환경을 사용하고 있습니다. 
낮은 JDK버젼의 환경에서도 Apache HTTP Client등의 라이브러리를 사용하여 쉽게 HTTP요청 흐름의  코드를 구현할 수 있지만, 
보안레벨이 높은 서버의 경우 Jar파일하나 업로드하는 것도 상사의 허락을 받아야하는 등 라이브러리를 추가하는게 어렵습니다.   

따라서 (1) 낮은 버젼의 JDK환경에서도 실행가능하며며,  Postman과 같은 어플리케이션 처럼 (2) 자유자재로 설정값을 지정하여 HTTP Request 송신가능 하며 (3) 외부 라이브러리없이 실행가능한 모듈을 만들어 두면
프로토타입의 배치나 API를 개발하는데 매우 편하고 빠르게 개발이 가능할 것이므로 HttpURLConnection을 사용해서 HTTP요청 흐름을 처리하는 모듈을 구현하려 합니다. 

2) 어떤 프로그램을 만들려고 하는지 (요구사항)

일반적인 HTTP요청 흐름은 다음과 같습니다.  
 (1) Request준비 
  - URL 설정
  - Method (GET, POST, UPDATE, DELETE...) 설정
  - Request Header 설정 
  - Request Body 설정 (POST, UPDATE등의 요청시)
  - 기타 설정(Timeout Limit등..)
 
(2) Request 송신하기 
 
 (3) Reponse 처리
  - Response Code확인 2xx, 3xx, 4xx, 5xx
  - Response 결과가 성공 : Response Body의 데이터 처리등 상정한 처리 실시 
  - Response 결과가 실패 : Response Code에 해당하는 메시지를 출력
 
 위의 HTTP 요청흐름에서 (1)과 (3)의 경우 어떤 API에 어떤 Request를 보내는지에 따라 처리내용의 변경이 발생합니다. 


 예를 들어 A사와 B사의 API가 있다고 가정해 봅니다.

A사의 API는 POST방식 Method로 Request시에 Request Body에 JSON데이터를 담아서 전송해야 Response가 성공하며 Response 시에는 Response Body에 JSON데이터가 담겨집니다.
B사의 API에 POST방식 Method로 Request시에 Request Body에 XML데이터를 담아서 전송해야 Response가 성공하며 Response 시에는 Response Body에 JSON데이터가 담겨집니다. 

위의 예와 같은 상황이라면  

아래의 2가지를 알 수 있습니다. 

(1) A사와 B사의 API별로 각각 다른 데이터를 Request Body에 설정을 해야 정상처리가 된다.

(2) A사와 B사의 API가 리턴하는 Response Body 데이터 값이 다르므로, 각각 다른 처리를 해야한다.  

 

따라서 모듈의 기능을 사용시에는 Request 설정과 Response 데이터처리를 동적으로 설정가능하며, 확장이 가능하게 해야한다고 생각했습니다.  


2. 코드 

HTTP 요청 흐름의 (3) Reponse 처리도 Response Body의 데이터에 따라서 다른 프로세스가 필요하므로 분리하여 확장가능하게 구현해야 한다고 생각했습니다. 
 
 - 모듈 

public class HttpUtil {
	
	private HttpUtil() {};
	public static HttpUtil getInstance() { return HttpUtilHolder.INSTANCE; }
	private static class HttpUtilHolder { private static final HttpUtil INSTANCE = new HttpUtil(); }
	
	
	private HttpURLConnection con;
	private byte[] formData = null;
	private int CONNECTION_TIMEOUT_LIMIT = 0;
	private int READ_TIMEOUT_LIMIT       = 0;
	private RequestBody requestBody;
	private ResponseBody responseBody;
	

	public void setRequestBody(RequestBody requestBody) { this.requestBody = requestBody; }
	public RequestBody getRequestBody() { return requestBody; }
	public void setResponseBody(ResponseBody responseBody) { this.responseBody = responseBody; }
	public ResponseBody getResponseBody() { return responseBody; }
	
	
	/**
	 * HTTP Request 메인
	 * 
	 * @param protocol
	 * @param targetUrl
	 * @param settings
	 * @return
	 */
	public boolean request(String protocol, String targetUrl, Map<String, String> settings) {
		
		try {
			// URL에 해당하는 Connection 객체 생성
			setConnection(protocol, targetUrl);
			
			// Request 준비
			requestSetting(settings);
			
			// Request 준비 - Request Body 설정
			if (settings.get("Method") != null && (settings.get("Method").equals("POST") || settings.get("Method").equals("PUT"))) {
				this.formData = requestBody.getData();
			}
			
			// Request 송신 & Response 처리
            return execute();
            
		} catch (Exception e) { 
			e.printStackTrace(); 
			return false;
		} 
	}
	
	
	/**
	 * Connection 설정
	 * 
	 * @param protocol
	 * @param targetUrl
	 * @return
	 * @throws IOException
	 */
	private void setConnection(String protocol, String targetUrl) throws IOException {
		URL url = new URL(targetUrl);
		switch (protocol) {
		case "http":
			this.con = (HttpURLConnection)url.openConnection();
			break;
		case "https":
			this.con = (HttpsURLConnection)url.openConnection();
			break;
		default:
			break;
		}
	}
	
	
	/**
	 * Request Header등 설정
	 * 
	 * @param settings
	 * @throws ProtocolException
	 */
	private void requestSetting(Map<String, String> settings) throws ProtocolException {

		System.out.println("---------- Set Header Start----------");
		for (String headerInfoKey : settings.keySet()) {
			System.out.println("SetKey : "+headerInfoKey+", SetValue : "+settings.get(headerInfoKey) );
			this.con.setRequestProperty(headerInfoKey, settings.get(headerInfoKey));
			
		}
		System.out.println("---------- Set Header End  ----------");
		
		System.out.println("--------- Value Setting -------");
		this.con.setConnectTimeout(CONNECTION_TIMEOUT_LIMIT);
		this.con.setReadTimeout(READ_TIMEOUT_LIMIT);
		this.con.setUseCaches(false); 
		this.con.setInstanceFollowRedirects(settings.get("Method").equals("GET") ? true : false);
		this.con.setDoOutput(settings.get("Method").equals("GET") ? true : true);
	}
	
	
	/**
	 * Request 송신
	 * 
	 * @return
	 * @throws Exception 
	 */
	private boolean execute() throws Exception {
		
		BufferedReader buff = null;
		try {
			
			// 요청보내기
			this.con.connect();
			
			OutputStream os;
			if (this.formData != null) {
				os = this.con.getOutputStream();
				os.write(this.formData);
			}
			
			// 응답 확인하기
			int responseCode = this.con.getResponseCode();
			
			if(responseCode == HttpURLConnection.HTTP_OK) {
				buff = new BufferedReader(
						new InputStreamReader(this.con.getInputStream(),"utf-8"));
				
				if (!responseBody.readResult(buff)) {
					throw new Exception();
				}
				
			} else {
				System.err.println("ResponseCode : " + con.getResponseCode() +", ResponseMessage" + con.getResponseMessage());
				return false;
			}
			
			return true;
		
		} catch (Exception e) {
			e.printStackTrace();
			return false;
			
		} finally {
		    if (buff != null) {
		    	try {
		    		buff.close();
		    		buff = null;
		    		this.con.disconnect();
		    		this.con = null;
				} catch (Exception e) { e.printStackTrace(); }
		    }
		}
	}
	
}

 메인처리 메서드에서는 'HTTP 요청흐름의 순서대로' 각 처리가 진행되게 됩니다. 

Reqeust설정에서 Request Body이외의 설정값은 Map에 담아서 설정하도록 했습니다.

Request Body의 데이터 설정 처리는 인터페이스의 구현객체를 Set메소드로 받아서 처리하도록 했습니다.  

Response Body의 데이터 처리도 인터페이스의 구현객체를 Set메소드로 받아서 처리하도록 했습니다.

 
 - Request 처리를 위한 인터페이스 

public interface RequestBody {

	void setData(Object data) throws UnsupportedEncodingException;
	void setData(Object data, String originEncoding, String newEncoding) throws UnsupportedEncodingException;
	void setData(File target) throws UnsupportedEncodingException, IOException;
	void setData(File target, String originEncoding, String newEncoding) throws UnsupportedEncodingException, IOException;
	byte[] getData();
	
}

HttpURLConnection에서 Request Body에 데이터를 담을 때는 byte배열로 담아야 하므로,

setData메서드를 통해서 Reqeust Body에 담을 데이터를 받아서 byte배열로 변환하고, 변환된 byte배열을 getData메서드로 가져올 수 있도록 했습니다.    


 - Response 처리를 위한 인터페이스  

public interface ResponseBody {

	boolean readResult(BufferedReader buff) throws IOException;
	Object getResult() throws IOException;
}

HttpURLConnection의 InputStream에서 Response Body 데이터를 가져와 Buffered Reader로 출력하는 readResult메서드를 정의했습니다. 

처리가 끝난 데이터는 getResult메서드로 가져올 수 있도록 했습니다.

 

 

3. 전체코드

https://github.com/leeyoungseung/template-java

반응형

댓글