📌 StreamUtils.copyToString() — InputStream을 String으로 쉽게 변환하는 방법

Spring 기반 웹 애플리케이션을 개발하다 보면, 클라이언트로부터 전달받은 HTTP 요청의 InputStream을 문자열로 읽어야 할 때가 있습니다. 이때 유용하게 사용할 수 있는 도구가 바로 StreamUtils.copyToString()입니다.


🔍 StreamUtils.copyToString()이란?

StreamUtils.copyToString()은 Spring Framework에서 제공하는 유틸리티 메서드로, InputStream으로부터 전달받은 바이트 데이터를 문자열(String)로 간편하게 변환해주는 기능을 합니다.

public static String copyToString(InputStream in, Charset charset) throws IOException
  • in: 읽을 대상 InputStream (예: 요청 본문)
  • charset: 변환 시 사용할 문자 인코딩 (예: StandardCharsets.UTF_8)

✅ 어떤 상황에서 쓰일까?

  • 클라이언트가 POST나 PUT 방식으로 JSON, XML, 혹은 일반 텍스트 데이터를 보낼 때
  • 필터나 인터셉터에서 요청 본문을 직접 읽어야 하는 경우
  • 서블릿에서 InputStream을 문자열로 바꾸고 싶을 때

💡 사용 예제

@PostMapping("/example")
public void example(HttpServletRequest request) throws IOException {
    String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
    System.out.println("요청 본문: " + body);
}

위 코드는 다음과 같은 요청에서:

POST /example
Content-Type: application/json

{"name":"철준", "age":35}

다음과 같은 출력 결과를 만듭니다:

요청 본문: {"name":"철준", "age":35}

⚠️ 주의할 점

  1. InputStream은 한 번만 읽을 수 있음!
    • copyToString()을 사용한 후에는 같은 InputStream을 다시 읽을 수 없습니다.
    • 여러 번 사용하려면 RequestWrapper를 만들어 캐싱하거나 별도 처리를 해야 합니다.
  2. 인코딩 설정은 반드시 명시해야 함
    • 클라이언트가 보낸 데이터가 UTF-8이 아닐 경우 StandardCharsets.UTF_8로 읽으면 깨질 수 있습니다.
    • Content-Type의 charset 정보를 확인하거나, UTF-8로 통일되게 처리하는 것이 좋습니다.

📦 어디에 정의되어 있나?

StreamUtils는 Spring Core 모듈에 포함되어 있으며, 다음 패키지에 위치해 있습니다:

org.springframework.util.StreamUtils

의존성은 따로 추가할 필요 없이, 스프링 부트 프로젝트에서는 기본적으로 포함되어 있습니다.


✨ 요약

항목 설명
목적 InputStream → String 변환
위치 org.springframework.util.StreamUtils
주요 사용 예 요청 본문 읽기, JSON 처리, 필터/인터셉터 처리 등
주요 주의 사항 InputStream은 한 번만 읽을 수 있음, 인코딩 주의

✅ 마무리

StreamUtils.copyToString()은 간단하지만 스프링 개발에서 꽤 자주 쓰이는 핵심 유틸 메서드입니다.
특히 요청 본문을 직접 다루거나, 인터셉터에서 로그를 찍을 때 매우 유용하게 사용할 수 있으니 기억해두면 실무에서 큰 도움이 될 것 입니다.

 

 

 

참조:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/StreamUtils.html 

 

 

 

response.getWriter().write() vs return "ok"

Spring MVC를 처음 접하거나, 직접 서블릿 API를 다뤄본 개발자라면 한 번쯤은 다음 두 코드의 차이에 대해 궁금했던 적이 있을 것입니다:

// 방식 1: HttpServletResponse 사용
response.getWriter().write("ok");

// 방식 2: @ResponseBody와 return
@ResponseBody
public String hello() {
    return "ok";
}

이 두 방식은 모두 클라이언트에게 HTTP 응답을 반환하지만, 사용 방식과 처리 흐름에 큰 차이가 있습니다.

이번 글에서는 이 차이를 정리하고, 어떤 상황에서 어떤 방식을 사용하는 것이 좋은지 알아보겠습니다.


1. response.getWriter().write("ok"): 서블릿 API 직접 사용

이 방식은 HttpServletResponse 객체를 통해 직접 응답 본문에 값을 써넣는 저수준 방식입니다.

@Controller
public class ResponseController {

    @RequestMapping("/manual-response")
    public void manualResponse(HttpServletResponse response) throws IOException {
        response.getWriter().write("ok");
    }
}

특징

  • HttpServletResponse의 getWriter()를 통해 PrintWriter를 얻고, 직접 문자열을 씁니다.
  • Spring의 뷰 리졸버(ViewResolver)나 HttpMessageConverter를 거치지 않습니다.
  • 스프링의 생태계를 우회하고 직접 응답을 제어해야 할 때 사용합니다.
  • 예: 파일 다운로드, 이미지 응답, 바이너리 응답 등.

2. return "ok" + @ResponseBody: 스프링 방식의 간결한 처리

반면, 스프링 MVC에서는 메소드 반환값을 HTTP 응답으로 보내고 싶을 때 @RestController 또는 @ResponseBody를 활용할 수 있습니다.

@RestController
public class RestApiController {

    @GetMapping("/spring-response")
    public String springResponse() {
        return "ok";
    }
}

 

※ 또는 @Controller에 @ResponseBody를 붙이면 동일한 효과입니다.

@Controller
public class RestApiController {

    @ResponseBody
    @GetMapping("/spring-response")
    public String springResponse() {
        return "ok";
    }
}

특징

  • @ResponseBody가 붙으면 반환값은 뷰 이름으로 인식되지 않고, HTTP 응답 본문으로 전송됩니다.
  • 내부적으로 HttpMessageConverter가 동작하여 문자열, JSON 등으로 자동 변환됩니다.
  • RESTful API나 간단한 문자열 응답을 만들 때 매우 유용합니다.

3. 어떤 방식을 써야 할까?

기준 response.getWriter().write() @ResponseBody + return
처리 수준 저수준 (서블릿 직접 제어) 고수준 (스프링에서 처리)
코드 간결성 비교적 복잡 매우 간결
뷰/메시지 변환기 사용 여부 사용하지 않음 HttpMessageConverter 사용
사용 사례 파일 다운로드, 이미지 응답 등 문자열, JSON 응답 등 일반적인 API 응답
추천 여부 특별한 제어가 필요한 경우만 추천 대부분의 경우 적극 추천

4. 정리

  • 직접 제어가 필요한 특별한 상황(파일 처리, 비동기 스트림 등)이 아니라면, 스프링이 제공하는 @ResponseBody 방식이 훨씬 간편하고 유지보수에 유리합니다.
  • REST API 개발 시에는 @RestController를 사용하면 @ResponseBody가 자동 적용되므로 더욱 편리합니다.
  • 스프링은 단순히 편리함을 넘어, 개발자의 생산성과 유지보수성을 높여주는 강력한 추상화 도구임을 기억하면 좋습니다.

 

참고 링크

객체지향 프로그래밍(OOP) 이해하기

객체지향 프로그래밍(OOP)은 현실 세계를 객체로 추상화하여 소프트웨어를 설계하는 방법론이다. 코드의 재사용성을 높이고 유지보수를 쉽게 만들어 백엔드 개발에서 자주 사용된다.

 

객체지향 프로그래밍의 4대 원칙

1. 캡슐화 (Encapsulation):

  • 데이터를 외부에서 접근할 수 없도록 숨기고, 필요한 기능만 공개.
  • 데이터와 메서드를 객체 안에 묶어 관리.

예제: 

class Car {
  constructor(brand) {
    this.brand = brand; // private data
  }

  getBrand() {
    return this.brand; // public method
  }
}

const myCar = new Car("Tesla");
console.log(myCar.getBrand()); // "Tesla"

 

 

2. 추상화 (Abstraction):

  • 객체의 복잡성을 감추고, 필요한 핵심 기능만 노출.
  • 불필요한 내부 구현을 감추어 사용자에게 단순한 인터페이스 제공.

예제:

class Payment {
  processPayment(amount) {
    console.log(`Processing payment of $${amount}`);
  }
}

const payment = new Payment();
payment.processPayment(100); // 세부 구현은 감춰짐
 
3. 상속 (Inheritance):
  • 부모 클래스의 속성과 메서드를 자식 클래스가 물려받아 사용.
  • 코드 재사용성을 극대화.

예제:

class Animal {
  speak() {
    console.log("Animal speaks");
  }
}

class Dog extends Animal {
  speak() {
    console.log("Dog barks");
  }
}

const myDog = new Dog();
myDog.speak(); // "Dog barks"
 
 
4. 다형성 (Polymorphism):
  • 같은 이름의 메서드가 객체에 따라 다르게 동작.
  • 메서드 오버라이딩(Overriding)을 통해 구현.

예제:

class Shape {
  draw() {
    console.log("Drawing a shape");
  }
}

class Circle extends Shape {
  draw() {
    console.log("Drawing a circle");
  }
}

const shapes = [new Shape(), new Circle()];
shapes.forEach((shape) => shape.draw());
// Output:
// "Drawing a shape"
// "Drawing a circle"

 

캡슐화의 중요성

  • 데이터를 보호하여 외부 접근으로 인한 무결성 훼손 방지.
  • 인터페이스를 통해 객체의 사용 방법을 통제.

실생활 비유:

은행의 ATM은 내부 로직을 숨기고, 사용자에게 단순한 "돈 인출" 버튼만 제공

 

추상화의 활용

  • 복잡한 구현을 숨기고, 중요한 부분만 드러냄으로써 코드의 복잡성을 줄임.
  • 핵심 메서드만 사용자에게 노출.

실생활 비유:

자동차 운전자는 내부 엔진의 동작 방식을 몰라도 "운전대"와 "가속 페달"만으로 자동차를 조작할 수 있음.

 

상속과 코드 재사용

  • 상속을 통해 부모 클래스의 기능을 확장하고 새로운 기능을 추가.
  • 코드를 중복 작성하지 않고, 유지보수를 쉽게 만듦.

실생활 비유:

"동물"이라는 기본 클래스를 만들고, 이를 상속받아 "개", "고양이" 등의 세부 동물 클래스를 생성.

 

객체지향 프로그래밍의 장점

  1. 코드 재사용성 증가:
    중복 코드를 줄이고, 재사용 가능한 컴포넌트를 생성.
  2. 유지보수 용이:
    객체 단위로 코드를 관리하여 수정이 쉬움.
  3. 확장성:
    새로운 기능 추가 시 기존 코드를 최소한으로 수정.
  4. 현실 세계 모델링:
    현실 세계의 개념과 동작을 코드로 직관적으로 표현.

 

결론

객체지향 프로그래밍(OOP)은 코드의 재사용성, 유지보수성, 확장성을 높이는 데 매우 유용한 방법론이다.

캡슐화, 추상화, 상속, 다형성 등 핵심 원칙을 활용하여 효율적이고 직관적인 백엔드 시스템을 구축할 수 있다.

 

 

 

참조:
https://m.blog.naver.com/tkdldjs35/221299078193

https://www.youtube.com/watch?v=M3zlc6jiB5o

https://inpa.tistory.com/entry/OOP-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%B6%94%EC%83%81%ED%99%94-%EC%84%A4%EA%B3%84%EC%9D%98-%EC%9D%B4%ED%95%B4

Node.js란 무엇인가?

Node.jsChrome V8 JavaScript 엔진을 기반으로 만든 JavaScript 런타임 환경이다.
주로 서버 사이드 애플리케이션 개발에 사용되며, 다음과 같은 특징을 가진다.

  • 비동기 이벤트 기반으로 고성능 처리 가능.
  • 프런트엔드와 백엔드에서 동일한 JavaScript 언어를 사용 가능.
  • 파일 관리, 데이터베이스 연동, HTTP 서버 등 다양한 기능을 지원.

 

Node.js의 구조

Node.js는 클라이언트-서버 모델에서 주로 사용된다.

  1. Client-side:
    • 브라우저에서 실행되는 JavaScript 코드.
    • 사용자 인터페이스를 담당.
  2. Server-side:
    • 서버에서 실행되는 Node.js 코드.
    • 데이터 처리, API 제공, 데이터베이스 연동 등 수행.

Shared Code

Node.js를 사용하면 클라이언트와 서버 간 코드를 공유하여 생산성을 높일 수 있다.

 

 

Node.js의 강점

  • 비동기 처리:
    요청이 많아도 빠른 응답을 제공.
  • 확장성:
    모듈 시스템으로 원하는 기능만 추가 가능.
  • JavaScript 활용:
    프런트엔드와 백엔드 모두 동일한 언어로 개발 가능.

 

 

참조: 
https://nodejs.org/en
https://poiemaweb.com/nodejs-basics

+ Recent posts