프론트에서 ajax나 Fetch API를 활용해서 API 통신하는 방식은 익숙하다. 백엔드에서도 URLConnection을 생성해서 API 통신을 할 수 있고 Response의 Payload 데이터에 접근할 수 있다!
예제코드
@PostMapping("/api/test")
public ResponseEntity urlConnectResponseTest(@RequestBody JSONObject param) {
HashMap res = new HashMap<>();
List<String> list = new ArrayList<>();
list.add("1. 테스트 데이터입니다.");
list.add("2. 테스트 데이터입니다.");
list.add("3. 테스트 데이터입니다.");
res.put("code", 200);
res.put("message", "success");
res.put("data", list);
return ResponseEntity.ok(res);
}
테스트 데이터를 반환하는 간단한 API 하나를 생성하였다. 보통 JSON 데이터를 Request Body에 넘겨주는 경우가 많기 때문에 동일한 환경으로 설정하였다. Status Code는 200을 반환, 예외의 경우는 따로 설정하지 않았다.
@Test
void Connection() throws Exception{
String testUrl = "http://localhost:8082/api/test";
JSONArray result = new JSONArray();
URL url = new URL(testUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
JSONObject param = new JSONObject(map);
}
먼저 JSON 데이터를 쉽게 핸들링하기 위해 관련 디펜던시(JSON.simple)를 추가하였다. 정상적으로 통신이 완료되면 result 변수에 Payload 데이터를 가져올 것이다!
Class URL
연결을 위한 URL Object를 생성한다. URL은 Serializable를 구현하고 있기 때문에 직렬화, 역직렬화가 가능하다. URL 인스턴스를 생성할 때 파라미터 값으로 커넥션 할 url을 통째로 넘겨주거나 내부에 정의되어 있는 Construct에 따라 url을 여러 부분으로 구분하여 생성할 수도 있다.
이제 openConnection()을 호출해서 URLConnection 인스턴스를 생성해야 할 차례이다. openConnection()을 호출한다고 해서 실제 네트워크 연결이 설정되는 것은 아니고 URLConnection의 connect()를 호출해야 네트워크 연결이 설정된다. 그래서 HTTP 프로토콜의 URLConnnection(HttpURLConnection) 객체를 생성하고 Request Body에 넘겨줄 JSON 형식의 데이터를 OutputStream에 담아 flush하면 OutputStream에 정상적으로 반영되고 connect()을 호출하면 정상적으로 통신이 완료된다. 그리고 InputStream으로 Response 반환 데이터를 읽어오면 Payload 데이터에 접근할 수 있게 된다!
파라미터로 만들 Map은 따로 정한 양식은 없기 때문에 임의의 값(key, value)으로 설정했다. 그렇게 Map 컬렉션에 파라미터 값을 넣은 후 JSONObject로 변환해주었다. 이 JSON 데이터를 Request Body에 담아 보낼 것이다.
405 ERROR
예제코드 작성 중 Response Code가 자꾸 405로 반환됐다. Method Not Allowed: 비허가된 방식이라는 건데 작성한 API의 HTTP 메소드와 커넥션의 요청 메소드도 올바르게 작성하였다. 그렇게 당일까지 되지 않다가 다음날 다시 수정하니 정상적으로 되었다. 원인을 분석해 보자면 이렇다.
- HTTP GET 메소드 스펙에 Body를 사용하고자 했다.
MDN에 나와있는 GET Method 정보에 의하면 GET Method에 Payload를 담는 게 사양 측면에서 금지하진 않지만 의미는 정의되지 않았기 때문에 일부 기존 구현에서 요청을 거부할 수 있다는 내용이 있다. 즉, Return을 항상 보장받을 수 없다면 Body 속성을 이용해야 하는 경우엔 GET Method를 사용할 이유가 없다.
@Test
void Connection() throws Exception{
String testUrl = "http://localhost:8082/api/test";
JSONArray result = new JSONArray();
URL url = new URL(testUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
JSONObject param = new JSONObject(map);
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
}
URLConnection에서 지원하는 프로토콜에 대한 속성 설정을 이용했다.
1. setUseCaches(): HTTP 프로토콜이 지원하는 캐시 사용여부를 설정한다.
2. setDoInput(boolean doInput): 연결된 커넥션에서 InputStream을 이용여부 설정한다. 기본 값은 true이다.
3. setDoOutput(boolean doOutput): 연결된 커넥션에서 OutputStream을 이용여부 설정한다. 기본 값은 false이다.
4. setRequestMethod(String method): 통신하는 API의 HTTP Method를 기재한다.
5. setRequestProperty: 커넥션에서 이용할 Property를 설정할 수 있다. 주로 Header에 설정할 값이나 파라미터가 필요한 GET Method를 이용할 경우 사용한다.
이제 통신에 필요한 커넥션 속성을 다 설정했고, Body에 JSON 데이터를 넣기 위한 OutputStreamWriter와 정상적으로 통신이 되면 Response 데이터를 읽기 위한 BufferdReader를 생성한다.
@Test
void Connection() throws Exception{
String testUrl = "http://localhost:8082/api/test";
JSONArray result = new JSONArray();
URL url = new URL(testUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
Map<String, String> map = new HashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
JSONObject param = new JSONObject(map);
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
OutputStreamWriter wr = null;
BufferedReader br = null;
try {
wr = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
wr.write(param.toString());
wr.flush();
conn.connect();
int resCode = conn.getResponseCode();
br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
String bufLine;
StringBuffer bf = new StringBuffer();
while((bufLine = br.readLine()) != null) {
bf.append(bufLine.trim());
}
String resultStr = bf.toString();
JSONParser jsonParser = new JSONParser();
if(resCode == 200) {
JSONObject jsonObject = (JSONObject) jsonParser.parse(resultStr);
System.out.println("JsonObject : " + jsonObject.toJSONString());
result = (JSONArray) jsonObject.get("data");
System.out.println("result : " + result.toString());
}
}catch (NullPointerException e) {
log.error("NullPointException : " + e);
}catch (IOException e) {
log.error("IOException : " + e);
}finally {
if(wr != null) wr.close();
if(br != null) br.close();
conn.disconnect();
}
}
예외를 잡아주는 선 작업을 진행하였다. Stream에 반환되는 값이 없을 경우 생길 수 있는 NullPointExcepion과 입출력 과정에서 생길 수 있는 IOException을 함께 잡아주었다. 그리고 마지막으로 사용한 Stream을 닫고 연결된 커넥션을 disconnect 했다.
1. new OutputStreamWriter(URLConnection.getOutputStream(), "UTF-8"): Stream에 넘겨줄 데이터를 작성하기 위한 Writer를 생성한다. 연걸하려는 커넥션의 OutputStream을 인자 값으로 넘겨준다.
2. write(): Stream에 데이터를 작성한다.
3. flush(): 변경된 사항을 Stream에 적용한다. 해당 메소드를 호출하지 않으면 400 Code를 리턴할 것이다.
이제 connect()를 호출해서 연결을 진행한다. 정상적으로 연결이 되었다면 설정한 대로 Response Code는 200 Code를 리턴할 것이다. 200 코드를 반환받으면 Payload에 접근하여 원하는 데이터를 result에 담을 것이다. 먼저는 InputStreamReader를 BufferdReader로 감싸서 반환된 데이터를 읽을 것이다. 그리고 모든 데이터를 StringBuffer에 append 하여 JSON 형식의 문자열 데이터를 만들고 Key - Value 형태의 컬렉션으로 이용하기 위해서 JSONParser - JSONObject로 형변환을 진행하였다.
Reference
https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET
https://docs.oracle.com/javase/8/docs/api/
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405
'Java' 카테고리의 다른 글
[java] Stream API Basic (0) | 2023.02.20 |
---|---|
[java] JVM의 Runtime Data Area에 대한 탐구 (0) | 2023.01.22 |