프로그램/Java

[Java] HttpURLConnection으로 HTTP/HTTPS 연결 구현하기

neorc 2025. 2. 26. 16:30
JDK에 기본으로 포함된 HttpURLConnection 클래스만으로도 충분히 HTTP와 HTTPS 연결을 구현할 수 있다는 사실을 알고 계셨나요? 복잡한 외부 의존성 없이 순수 자바로 HTTP/HTTPS 연결을 구현하는 방법을 알아보겠습니다.

 

목차
1. [HttpURLConnection vs 외부 라이브러리](#httpurlconnection-vs-외부-라이브러리)
2. [HTTP GET 요청 구현 (파라미터 포함)](#http-get-요청-구현)
3. [HTTP POST 요청 구현 (JSON 데이터 전송)](#http-post-요청-구현)
4. [HTTPS 연결 시 자주 발생하는 문제와 해결법](#https-연결-시-자주-발생하는-문제와-해결법)
5. [타임아웃 설정과 성능 최적화](#타임아웃-설정과-성능-최적화)
6. [자주 묻는 질문(FAQ)](#자주-묻는-질문)

 

HttpURLConnection vs 외부 라이브러리

"왜 HttpURLConnection을 사용해야 할까요?" 많은 개발자들이 가장 먼저 던지는 질문입니다.

HttpURLConnection의 장점

  • 별도의 의존성 추가 필요 없음 - JDK에 기본 내장
  • 가벼운 메모리 사용량 - 외부 라이브러리보다 리소스 사용이 적음
  • 단순한 HTTP 요청에 적합 - 간단한 API 호출에 불필요한 오버헤드 제거
  • 레거시 시스템 호환성 - 오래된 자바 버전에서도 사용 가능

외부 라이브러리(OkHttp, Apache HttpClient 등)가 더 나은 경우

  • 복잡한 HTTP 요청이 많은 경우
  • 동시에 많은 연결을 처리해야 하는 경우
  • 고급 기능(캐싱, 쿠키 관리 등)이 필요한 경우

HTTP GET 요청 구현

실무에서 가장 많이 사용되는 GET 요청 구현 방법을 살펴보겠습니다. 특히 URL 파라미터를 포함한 요청 방법에 초점을 맞추겠습니다.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

public class HttpGetExample {
    
    public static void main(String[] args) {
        try {
            // 파라미터 준비
            Map<String, String> parameters = new HashMap<>();
            parameters.put("name", "홍길동");
            parameters.put("age", "30");
            parameters.put("query", "자바 프로그래밍");
            
            // 파라미터가 포함된 URL 생성
            String baseUrl = "http://example.com/api/search";
            StringBuilder urlBuilder = new StringBuilder(baseUrl);
            urlBuilder.append("?");
            
            boolean first = true;
            for (Map.Entry<String, String> entry : parameters.entrySet()) {
                if (!first) {
                    urlBuilder.append("&");
                }
                urlBuilder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.toString()));
                urlBuilder.append("=");
                urlBuilder.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.toString()));
                first = false;
            }
            
            // URL 객체 생성
            URL url = new URL(urlBuilder.toString());
            
            // HttpURLConnection 객체 생성
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            
            // 요청 메소드 설정
            connection.setRequestMethod("GET");
            
            // 타임아웃 설정 - 실무에서 중요!
            connection.setConnectTimeout(5000); // 연결 타임아웃 5초
            connection.setReadTimeout(5000);    // 읽기 타임아웃 5초
            
            // 요청 헤더 설정
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("User-Agent", "Mozilla/5.0");  // 일부 서버는 User-Agent를 요구함
            
            // 응답 코드 확인
            int responseCode = connection.getResponseCode();
            System.out.println("응답 코드: " + responseCode);
            
            // 응답 읽기
            try (BufferedReader in = new BufferedReader(
                    new InputStreamReader(connection.getInputStream()))) {
                String line;
                StringBuilder response = new StringBuilder();
                
                while ((line = in.readLine()) != null) {
                    response.append(line);
                }
                
                System.out.println("응답 내용: " + response.toString());
            }
            
            // 연결 종료
            connection.disconnect();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

실무 팁: GET 요청 시 주의사항

  • 항상 파라미터를 URL 인코딩하세요 - 특수문자나 한글이 포함된 경우 반드시 필요
  • User-Agent 헤더 설정 - 일부 서버는 봇 요청을 차단하므로 브라우저처럼 보이게 설정
  • try-with-resources 사용 - 자원 누수를 방지하기 위해 자동 닫기 활용
  • 응답 인코딩 확인 - 서버 응답의 문자 인코딩이 UTF-8인지 확인하고 적절히 처리

HTTP POST 요청 구현

REST API 연동 시 가장 많이 사용되는 JSON 데이터 전송을 위한 POST 요청 구현 방법입니다.

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;

public class HttpPostExample {
    
    public static void main(String[] args) {
        try {
            // URL 객체 생성
            URL url = new URL("http://example.com/api/users");
            
            // HttpURLConnection 객체 생성
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            
            // 요청 메소드 설정
            connection.setRequestMethod("POST");
            
            // 출력 스트림 활성화 (POST 데이터 전송을 위해 필요)
            connection.setDoOutput(true);
            
            // 타임아웃 설정
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            
            // 요청 헤더 설정
            connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
            connection.setRequestProperty("Accept", "application/json");
            
            // JSON 데이터 준비
            String jsonInputString = "{"
                + "\"name\": \"홍길동\","
                + "\"email\": \"hong@example.com\","
                + "\"age\": 30,"
                + "\"address\": {"
                + "  \"city\": \"서울\","
                + "  \"zipcode\": \"12345\""
                + "}"
                + "}";
            
            // POST 데이터 전송
            try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
                byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
                wr.write(input, 0, input.length);
            }
            
            // 응답 코드 확인
            int responseCode = connection.getResponseCode();
            System.out.println("응답 코드: " + responseCode);
            
            // 정상 응답과 에러 응답을 구분하여 처리 - 실무에서 매우 중요!
            if (responseCode >= 200 && responseCode < 300) {  // 성공 응답
                try (BufferedReader br = new BufferedReader(
                        new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
                    String line;
                    StringBuilder response = new StringBuilder();
                    while ((line = br.readLine()) != null) {
                        response.append(line);
                    }
                    System.out.println("성공 응답: " + response.toString());
                }
            } else {  // 에러 응답
                try (BufferedReader br = new BufferedReader(
                        new InputStreamReader(connection.getErrorStream(), StandardCharsets.UTF_8))) {
                    String line;
                    StringBuilder response = new StringBuilder();
                    while ((line = br.readLine()) != null) {
                        response.append(line);
                    }
                    System.out.println("에러 응답: " + response.toString());
                }
            }
            
            // 연결 종료
            connection.disconnect();
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

실무 팁: POST 요청 시 주의사항

  • 반드시 setDoOutput(true) 설정 - POST 데이터 전송에 필수
  • 정확한 Content-Type 헤더 설정 - 특히 JSON 데이터 전송 시 application/json; charset=UTF-8 필수
  • 에러 응답 처리 - 상태 코드에 따라 getInputStream() 또는 getErrorStream()을 사용
  • try-with-resources 사용 - 모든 스트림을 안전하게 닫기

HTTPS 연결 시 자주 발생하는 문제와 해결법

HTTPS 연결 시 개발자들이 자주 겪는 인증서 관련 문제와 해결 방법을 알아보겠습니다.

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.security.KeyStore;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class HttpsExample {
    
    public static void main(String[] args) {
        try {
            // 1. 기본 HTTPS 요청 (대부분의 공개 API에 적합)
            URL url = new URL("https://api.github.com/users/octocat");
            HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
            
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(5000);
            connection.setReadTimeout(5000);
            
            int responseCode = connection.getResponseCode();
            System.out.println("응답 코드: " + responseCode);
            
            // 응답 읽기
            if (responseCode == HttpsURLConnection.HTTP_OK) {
                try (BufferedReader in = new BufferedReader(
                        new InputStreamReader(connection.getInputStream()))) {
                    String line;
                    StringBuilder response = new StringBuilder();
                    while ((line = in.readLine()) != null) {
                        response.append(line);
                    }
                    System.out.println("응답 내용: " + response.toString());
                }
            }
            
            // 연결 종료
            connection.disconnect();
            
        } catch (Exception e) {
            System.out.println("HTTPS 연결 오류: " + e.getMessage());
            e.printStackTrace();
            
            // 오류 해결 가이드 제공
            if (e.getMessage().contains("PKIX path building failed") || 
                e.getMessage().contains("unable to find valid certification path")) {
                System.out.println("\n[인증서 오류 해결 방법]");
                System.out.println("1. 서버의 인증서를 Java 키스토어에 추가하세요:");
                System.out.println("   $ keytool -import -alias example -keystore $JAVA_HOME/lib/security/cacerts -file server.crt");
                System.out.println("2. 또는 개발/테스트용으로만 인증서 검증을 비활성화하는 코드를 사용하세요 (프로덕션 환경에서는 권장하지 않음)");
            }
        }
    }
    
    // 개발 환경에서만 사용할 인증서 검증 비활성화 메소드 (보안 위험이 있으므로 프로덕션에서는 사용하지 마세요!)
    private static void disableCertificateValidation() throws Exception {
        // 모든 인증서를 신뢰하는 TrustManager 생성
        TrustManager[] trustAllCerts = new TrustManager[] {
            new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() { return null; }
                public void checkClientTrusted(X509Certificate[] certs, String authType) { }
                public void checkServerTrusted(X509Certificate[] certs, String authType) { }
            }
        };
        
        // SSL 컨텍스트 설정
        SSLContext sc = SSLContext.getInstance("TLS");
        sc.init(null, trustAllCerts, new java.security.SecureRandom());
        HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
        
        // 호스트명 검증 비활성화
        HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);
    }
    
    // 프로덕션 환경에서 사용할 사용자 정의 키스토어 설정 메소드
    private static void setupCustomTrustStore(String trustStorePath, String trustStorePassword) throws Exception {
        System.setProperty("javax.net.ssl.trustStore", trustStorePath);
        System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
    }
}

HTTPS 연결 문제 해결 가이드

1. "PKIX path building failed" 오류 해결

이 오류는 서버의 SSL 인증서를 Java가 신뢰하지 않을 때 발생합니다.

해결 방법:

  • 프로덕션 환경: 서버 인증서를 Java 키스토어에 추가
keytool -import -alias example -keystore $JAVA_HOME/lib/security/cacerts -file server.crt

 

  • 개발/테스트 환경: Java 시스템 속성 설정
System.setProperty("javax.net.ssl.trustStore", "path/to/custom/truststore"); 
System.setProperty("javax.net.ssl.trustStorePassword", "password");

2. "Hostname verification failed" 오류 해결

이 오류는 인증서의 도메인명과 실제 접속 URL의 도메인명이 일치하지 않을 때 발생합니다.

해결 방법:

  • 올바른 도메인명으로 접속하거나
  • 개발/테스트 환경에서는 호스트명 검증 비활성화 (프로덕션에서는 권장하지 않음)
     
HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true);

타임아웃 설정과 성능 최적화

실무에서 HTTP 요청의 성능과 안정성을 높이기 위한 타임아웃 설정 방법과 성능 최적화 팁을 알아보겠습니다.

import java.net.HttpURLConnection;
import java.net.URL;

public class HttpOptimizationExample {
    
    public static void main(String[] args) {
        HttpURLConnection connection = null;
        
        try {
            URL url = new URL("http://example.com/api/resource");
            connection = (HttpURLConnection) url.openConnection();
            
            // 1. 기본 설정
            connection.setRequestMethod("GET");
            
            // 2. 타임아웃 설정 - 실무에서 매우 중요
            connection.setConnectTimeout(3000);  // 연결 타임아웃: 3초
            connection.setReadTimeout(10000);    // 읽기 타임아웃: 10초
            
            // 3. 캐싱 비활성화 (항상 최신 데이터 필요 시)
            connection.setUseCaches(false);
            
            // 4. 리다이렉션 자동 처리 (필요 시)
            connection.setInstanceFollowRedirects(true);
            
            // 5. Keep-Alive 설정 (연결 재사용)
            connection.setRequestProperty("Connection", "keep-alive");
            
            // 6. Gzip 압축 지원 (대용량 데이터 처리 시 효율적)
            connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
            
            // 7. 응답 코드 확인
            int responseCode = connection.getResponseCode();
            System.out.println("응답 코드: " + responseCode);
            
            // 응답 처리 (생략)...
            
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 항상 연결 종료
            if (connection != null) {
                connection.disconnect();
            }
        }
    }
}

성능 최적화 팁

  1. 적절한 타임아웃 설정
    • 연결 타임아웃(connectTimeout): 일반적으로 3-5초
    • 읽기 타임아웃(readTimeout): 응답 크기에 따라 10-30초
  2. 연결 재사용(Connection Pooling)
    • HttpURLConnection은 기본적으로 연결 풀링을 지원하지 않음
    • 대량의 요청이 필요한 경우 Apache HttpClient나 OkHttp 같은 라이브러리 고려
  3. 압축 활용
    • Accept-Encoding: gzip, deflate 헤더 추가
    • 응답 처리 시 GZIPInputStream을 통해 압축 해제
  4. 비동기 요청 처리
    • 다수의 요청을 병렬로 처리하려면 ExecutorService 활용
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Future<String>> futures = new ArrayList<>();

for (String apiUrl : urls) {
    futures.add(executor.submit(() -> sendHttpRequest(apiUrl)));
}

for (Future<String> future : futures) {
    String response = future.get();  // 응답 처리
}

자주 묻는 질문

Q1: HttpURLConnection과 HttpsURLConnection의 차이점은 무엇인가요?

A: HttpsURLConnection은 HttpURLConnection의 서브클래스로, SSL/TLS를 통한 보안 연결을 지원합니다. 기본 사용법은 동일하지만, HttpsURLConnection은 인증서 검증과 같은 추가 보안 기능을 제공합니다.

Q2: HttpURLConnection으로 파일을 업로드하는 방법은?

A: 멀티파트 폼 데이터(multipart/form-data)를 사용하여 파일을 업로드할 수 있습니다.

String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

try (OutputStream output = connection.getOutputStream();
     PrintWriter writer = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8), true)) {
    
    // 텍스트 파라미터 추가
    writer.append("--" + boundary).append("\r\n");
    writer.append("Content-Disposition: form-data; name=\"param\"").append("\r\n");
    writer.append("\r\n");
    writer.append("value").append("\r\n");
    
    // 파일 파라미터 추가
    writer.append("--" + boundary).append("\r\n");
    writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"").append("\r\n");
    writer.append("Content-Type: text/plain").append("\r\n");
    writer.append("\r\n");
    writer.flush();
    
    // 파일 데이터 복사
    Files.copy(Paths.get("path/to/file.txt"), output);
    output.flush();
    
    // 경계 종료
    writer.append("\r\n");
    writer.append("--" + boundary + "--").append("\r\n");
}

Q3: 어떤 경우에 OkHttp나 Apache HttpClient 같은 외부 라이브러리를 사용하는 것이 좋을까요?

A: 다음과 같은 경우 외부 라이브러리 사용을 고려하세요:

  • 자동 연결 풀링이 필요한 경우
  • 복잡한 인증 메커니즘(OAuth 등)이 필요한 경우
  • 재시도 로직, 인터셉터 등 고급 기능이 필요한 경우
  • 비동기/동시 요청이 많은 경우

Q4: HTTP 상태 코드를 어떻게 효과적으로 처리해야 할까요?

A: 상태 코드 범위별로 처리하는 것이 좋습니다:

  • 2xx (성공): 정상 응답 처리
  • 3xx (리다이렉션): 필요시 새 위치로 요청 재시도
  • 4xx (클라이언트 오류): 요청 수정 필요 (잘못된 인증, 권한 등)
  • 5xx (서버 오류): 일정 시간 후 재시도 로직 구현

Q5: HttpURLConnection의 성능을 개선하는 방법은?

A:

  1. 요청 간 Connection: keep-alive 헤더를 사용하여 연결 재사용
  2. 큰 응답을 처리할 때는 버퍼 크기 조정 (기본 8KB)
  3. 압축(gzip) 지원 활성화
  4. 응답 처리 시 BufferedReader/BufferedInputStream 사용
  5. 가능하면 ExecutorService를 사용하여 병렬 요청 처리

결론

자바의 HttpURLConnection은 외부 라이브러리 없이도 충분히 강력한 HTTP/HTTPS 통신을 구현할 수 있는 클래스입니다. 이 글에서 다룬 샘플 코드와 팁을 활용하면 대부분의 일반적인 API 통신을 구현할 수 있습니다.

복잡한 요구사항이나 대규모 애플리케이션에서는 OkHttp, Apache HttpClient 같은 전문 라이브러리를 고려해볼 수도 있지만, 간단한 HTTP 요청이나 외부 의존성을 최소화하고 싶은 경우 HttpURLConnection은 여전히 훌륭한 선택입니다.