웹사이트에서 정보를 가져오거나, 폼을 제출하거나, 파일을 업로드해야 하는 경우가 종종 있습니다.
이런 작업을 하려면 HTTP 요청을 보내고 응답을 처리하는 방법을 알아야 합니다.
이 글에서 다루는 내용:
- net/http로 간단한 HTTP 요청 보내기
- SSL 요청 처리하기
- POST 요청으로 데이터 제출하기
- 커스텀 헤더 보내기
- 상황에 맞는 최적의 HTTP 클라이언트 고르기
그럼 바로 시작해 보겠습니다!
HTTP 요청 보내는 기본 방법
Ruby에는 net/http라는 HTTP 클라이언트가 기본으로 내장되어 있어, 별도의 설치 없이도 필요한 모든 종류의 요청을 보낼 수 있습니다.
net/http 사용 예제입니다:
require 'net/http'
Net::HTTP.get('example.com', '/index.html')
이 코드는 해당 페이지의 HTML 콘텐츠가 담긴 문자열을 반환합니다.
하지만 실무에서는 HTML 본문만으로는 부족한 경우가 많습니다.
예를 들어 HTTP 응답 상태 코드가 필요합니다. 응답 상태를 확인하지 않으면 요청이 성공했는지 실패했는지 판단할 수 없기 때문입니다.
응답 상태를 얻는 방법은 다음과 같습니다:
response = Net::HTTP.get_response('example.com', '/')
response.code
# 200
응답 본문이 필요하다면 body 메서드를 호출하면 됩니다:
response.body
HTTParty 젬 활용하기
개발을 더 편하게 해주는 Ruby 젬(gem)은 매우 다양합니다. 그중 대표적인 것이 바로 httparty입니다.
사용 방법은 다음과 같습니다:
require 'httparty'
response = HTTParty.get('https://example.com')
response.code
# 200
response.body
# ...
HTTP 젬을 사용하면 얻을 수 있는 장점은 다음과 같습니다:
- 사용법이 훨씬 간결합니다.
- net/http처럼
get_response와get메서드가 분리되어 있지 않고,get호출만으로 바로 응답 객체를 받을 수 있습니다. - 다음 섹션에서 살펴보겠지만, SSL 요청도 자동으로 처리해 줍니다.
SSL 요청 보내기
net/http로 SSL(HTTPS) 요청을 그대로 보내면 어떻게 될까요?
Net::HTTP.get_response("example.com", "/", 443)
다음과 같은 에러가 발생합니다:
Errno::ECONNRESET: Connection reset by peer
정상적으로 동작하게 하려면 SSL 옵션을 직접 켜야 합니다:
net = Net::HTTP.new("example.com", 443)
net.use_ssl = true
net.get_response("/")
번거로운 설정을 피하고 싶다면, 이런 부분을 자동으로 처리해 주는 젬을 사용하는 것이 좋습니다. 🙂
POST 요청으로 데이터 제출하기
GET 요청은 정보를 요청할 때 사용합니다. 이미지, CSS, JavaScript 파일을 내려받는 경우가 대표적입니다.
반면 데이터를 서버로 전송하려면 POST 요청을 사용해야 합니다.
예제를 살펴보겠습니다:
HTTParty.post("https://example.com/login", body: { user: "test@example.com", password: "chunky_bacon" })
파일을 업로드하려면 multipart 요청이 필요한데, 아쉽게도 HTTParty는 multipart를 지원하지 않습니다.
이런 경우에는 rest-client 젬을 사용할 수 있습니다:
require 'rest-client'
RestClient.post '/profile', file: File.new('photo.jpg', 'rb')
또는 Faraday 젬을 사용하는 방법도 있습니다:
require 'faraday'
conn =
Faraday.new do |f|
f.request :multipart
f.request :url_encoded
f.adapter :net_http
end
file_io = Faraday::UploadIO.new('photo.jpg', 'image/jpeg')
conn.post('https://example.com/profile', file: file_io)
커스텀 HTTP 헤더 보내기
HTTP 요청에는 커스텀 헤더를 추가할 수 있습니다. 이를 통해 요청에 추가 정보를 함께 전달할 수 있으며, 대표적인 예는 다음과 같습니다:
- 쿠키(Cookies)
- User-agent
- 캐싱 관련 설정
- Content-type
- Accept (JSON 등 특정 형식의 응답 요청)
헤더를 지정하는 방법은 다음과 같습니다:
Faraday.new('https://example.com', headers: { 'User-Agent' => 'test' }).get
위 코드는 new로 Faraday 객체를 생성한 뒤, 그 객체에 get을 호출하는 방식입니다.
이렇게 작성할 수도 있습니다:
Faraday.post(
"https://example.com/login/oauth/access_token",
URI.encode_www_form(
code: params[:code],
client_id: ENV["example_client_id"],
client_secret: ENV["example_client_secret"],
),
{
"Content-Type" => "application/x-www-form-urlencoded",
"Accept" => "application/json"
}
)
이 패턴은 OAuth 방식의 요청을 처리할 때 특히 유용합니다.
나에게 맞는 Ruby HTTP 클라이언트 고르기
Ruby에서 사용할 수 있는 HTTP 클라이언트는 정말 많습니다. 그렇다면 어떤 것을 선택해야 할까요?
선택에 도움이 되도록 주요 젬들을 비교한 표를 준비했습니다.
| 저장소 | 스타 수 | 최근 커밋 수 | 마지막 커밋 | 최신 릴리스 | 생성일 |
|---|---|---|---|---|---|
| lostisland/faraday | 4817 | 26 | 2020-07-09 | 2020-03-29 | 2009-12-10 |
| rest-client/rest-client | 4913 | 0 | 2019-08-25 | 2019-08-21 | 2009-12-07 |
| typhoeus/typhoeus | 3739 | 2 | 2020-05-17 | 2020-01-15 | 2009-02-18 |
| jnunemaker/httparty | 5220 | 11 | 2020-06-10 | 2020-06-10 | 2008-07-28 |
| excon/excon | 1018 | 14 | 2020-06-30 | 2020-06-17 | 2009-10-25 |
| httprb/http | 2623 | 13 | 2020-03-30 | 2020-02-26 | 2011-10-06 |
인기도와 유지보수 활발함을 확인하는 것은 후보를 좁히는 좋은 첫걸음입니다. 하지만 기술적인 차이점도 살펴봐야겠죠?
위 젬 중 상당수는 net/http 라이브러리를 감싸는 래퍼(wrapper)입니다:
- excon
- httparty
- rest-client
반면 Faraday는 어댑터 방식의 젬으로, 다양한 구현체를 상황에 맞게 교체해서 사용할 수 있습니다. Typhoeus는 C 기반 libcurl 라이브러리의 래퍼입니다.
멀티스레딩에는 Typhoeus
Typhoeus는 직접 동시성(concurrency) 코드를 작성하지 않아도 동시 요청을 지원합니다.
따라서 초당 처리량을 최대화하고 싶다면 Typhoeus가 가장 좋은 선택입니다.
멀티스레딩과 함께 Typhoeus를 사용하는 예제입니다:
require 'typhoeus'
hydra = Typhoeus::Hydra.hydra
request = Typhoeus::Request.new("https://www.rubyguides.com")
hydra.queue(request)
hydra.queue(request)
hydra.run
유연성이 필요하다면 Faraday
Faraday는 net/http부터 Typhoeus, 심지어 rest-client까지 동일한 인터페이스로 어떤 구현체든 선택해서 사용할 수 있습니다.
기본값은 net/http이며, 다음과 같이 변경할 수 있습니다:
Faraday.default_adapter = :typhoeus
Faraday의 또 다른 매력은 미들웨어(middleware) 지원입니다. 미들웨어는 요청이나 응답을 원하는 방식으로 가공해 주는 일종의 플러그인이라고 생각하면 됩니다.
예제:
require 'faraday_middleware'
client =
Faraday.new do |f|
f.response :json
f.adapter :net_http
end
client.get('https://api.github.com/repos/vmg/redcarpet/issues')
이 미들웨어는 응답을 자동으로 JSON으로 파싱해 줍니다. 반대로 JSON을 파싱하는 게 아니라 전송하고 싶다면 f.response :json 대신 f.request :json을 사용하면 됩니다.
보너스: HTTP 요청 디버깅 방법
내 코드가 정확히 어떤 요청을 보내는지 눈으로 직접 확인하고 싶다면, 아주 유용한 트릭이 있습니다.
다음 한 줄짜리 명령어를 사용해 보세요:
ruby -rsocket -e "trap('SIGINT') { exit }; Socket.tcp_server_loop(8080) { |s,_| puts s.readpartial(1024); puts; s.puts 'HTTP/1.1 200'; s.close }
이 명령어는 8080 포트에서 서버를 띄워 수신한 모든 내용을 출력하고, HTTP 상태 코드를 반환한 뒤 연결을 종료합니다.
localhost:8080으로 요청을 보내면 그 내용을 그대로 확인할 수 있습니다.
예시:
Faraday.new('https://localhost:8080', headers: { 'User-Agent' => 'test', 'foo' => '1234' }).get
# GET / HTTP/1.1
# User-Agent: test
# Foo: 1234
# Accept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3
# Accept: */*
# Connection: close
# Host: localhost:8080
이 외에도 httplog 같은 젬이나 mitmproxy 같은 도구를 활용하는 방법도 있습니다.
마치며
이번 글에서는 정보를 조회하는 GET 요청부터 데이터를 전송하는 POST 요청까지, 다양한 종류의 HTTP 요청을 보내는 방법을 배웠습니다.
또한 Ruby에서 HTTP 요청을 다룰 때 가장 널리 쓰이는 젬들의 개요도 살펴봤습니다. 개인적으로는 유연성이 뛰어난 faraday를 가장 추천하지만, 여러분의 프로젝트에 더 잘 맞는 젬이 있다면 과감하게 그것을 선택하셔도 좋습니다.
이 글이 도움이 되었다면 주변 친구들에게도 공유해 주세요. 🙂
읽어주셔서 감사합니다!