Logo

curl 커맨드: 터미널에서 HTTP 호출하기

이번 포스팅에서는 터미널 상에서 간편하게 사용할 수 있는 HTTP 클라이언트인 curl 커맨드에 대해서 알아보겠습니다.

curl 커맨드

1998년에 만들어진 curl 커맨드는 Postman이나 Insomnia와 같이 다양한 기능과 화려한 UI를 제공하는 GUI 기반 HTTP 클라이언트가 계속해서 출시되는 와중에도 아직까지 꾸준히 사랑받고 있는 CLI 도구입니다. curl 커맨드는 리눅스나 MacOS에 대부분의 경우 기본 탑제되어 있고 몇가지 주요 옵션만 숙지하면 사용하기 매우 쉽기 때문입니다.

기본 HTTP 호출

웹 브라우저에서 어떤 사이트에 접속하듯이, GET 방식으로 HTTP 호출을 할 때는 아무런 옵션없이 curl 커맨드를 사용할 수 있습니다.

예를 들어, 공개된 REST API 서비스인 JSONPlaceholder의 글 상세 조회 endpoint를 호출해보겠습니다.

$ curl https://jsonplaceholder.typicode.com/posts/1
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}%

다른 방식으로 호출

GET 방식이 아닌 다른 방식(verb, method)으로 HTTP 호출을 할 때는 -X 또는 --request 옵션을 사용합니다.

예를 들어, 위에서 호출한 endpoint를 DELETE 방식으로 호출해보겠습니다.

$ curl https://jsonplaceholder.typicode.com/posts/1 -X DELETE
{}%

요청 바디/헤더 설정

POST나 PUT 방식으로 HTTP 호출을 할 때는 일반적으로 생성하거나 변경할 데이터를 서버에 보냅니다. -d 또는 --data 옵션을 사용하여 송신할 데이터를 설정해줄 수 있으며, -H 또는 --header 옵션을 통해 데이터 포멧을 명시해줄 수 있습니다.

$ curl https://jsonplaceholder.typicode.com/posts -d '{"title": "foo", "body": "bar", "userId": 1}' -H 'Content-type: application/json; charset=UTF-8'
{
  "title": "foo",
  "body": "bar",
  "userId": 1,
  "id": 101
}%

위 예제에서는 json 포멧의 데이터를 JSONPlaceholder 서비스의 글 생성 endpoint에 보내고 있습니다.

응답 헤더도 출력

curl은 기본적으로 응답 바디(response body)만을 HTTP 호출 결과로 콘솔에 출력해줍니다. 응답 헤더(response headers)까지 함께 확인하고 싶은 경우에는 -i 또는 --include 옵션을 사용하면 됩니다.

$ curl https://jsonplaceholder.typicode.com/posts/1 -i
HTTP/2 200
date: Tue, 20 Jul 2021 05:53:57 GMT
content-type: application/json; charset=utf-8
content-length: 292
x-powered-by: Express
x-ratelimit-limit: 1000
x-ratelimit-remaining: 999
x-ratelimit-reset: 1626031770
vary: Origin, Accept-Encoding
access-control-allow-credentials: true
cache-control: max-age=43200
pragma: no-cache
expires: -1
x-content-type-options: nosniff
etag: W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU"
via: 1.1 vegur
cf-cache-status: HIT
age: 28467
accept-ranges: bytes
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=j9WfyPrbmhLFD%2FGJY8Y0QkYHaF6LFmIwPXe8rSvKX9R1C%2BDnhq3wk%2B8J%2FQA7YUXm90%2BHawRyM%2FSkGXqBMz9S3Py1VvqZFrk6fcLn9i2dPvD79UjyGHczGfNVt9EF1yKt28sGEepW61Lg6OXjCQOy"}],"group":"cf-nel","max_age":604800}
nel: {"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 6719ee9bafc7d326-LAX
alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400, h3=":443"; ma=86400

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}%

응답 헤더만 출력

-I, --head 옵션을 사용하면 응답 바디없이 응답 헤더만 출력해줍니다.

$ curl https://jsonplaceholder.typicode.com/posts/1 -I
HTTP/2 200
date: Tue, 20 Jul 2021 06:03:39 GMT
content-type: application/json; charset=utf-8
content-length: 292
x-powered-by: Express
x-ratelimit-limit: 1000
x-ratelimit-remaining: 999
x-ratelimit-reset: 1626031770
vary: Origin, Accept-Encoding
access-control-allow-credentials: true
cache-control: max-age=43200
pragma: no-cache
expires: -1
x-content-type-options: nosniff
etag: W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU"
via: 1.1 vegur
cf-cache-status: HIT
age: 200
accept-ranges: bytes
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=OOghBjWfpaV%2FDvD6S5n9M3%2B5t5zSwqIGruGZCpbVmRXifdA8MCXHsrmUgdpmB9i80019QOIR%2BbaS5adnagtpk%2F4HXw8ZcHooWgIj6wC4hxStwgnRVWQ2tQtD0wSkMyAsjmgle10VrgSXvFyks2tB"}],"group":"cf-nel","max_age":604800}
nel: {"report_to":"cf-nel","max_age":604800}
server: cloudflare
cf-ray: 6719fcd5fe90e794-LAX
alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400, h3=":443"; ma=86400

상세 디버깅

-v 또는 --verbose 옵션과 함께 curl 커맨드를 사용하면 상세한 디버깅 정보가 출력됩니다.

$ curl https://jsonplaceholder.typicode.com/posts/1 -v
*   Trying 104.21.10.8...
* TCP_NODELAY set
* Connected to jsonplaceholder.typicode.com (104.21.10.8) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-ECDSA-CHACHA20-POLY1305
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=San Francisco; O=Cloudflare, Inc.; CN=sni.cloudflaressl.com
*  start date: Jun 28 00:00:00 2021 GMT
*  expire date: Jun 27 23:59:59 2022 GMT
*  subjectAltName: host "jsonplaceholder.typicode.com" matched cert's "*.typicode.com"
*  issuer: C=US; O=Cloudflare, Inc.; CN=Cloudflare Inc ECC CA-3
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fafba809200)
> GET /posts/1 HTTP/2
> Host: jsonplaceholder.typicode.com
> User-Agent: curl/7.64.1
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 256)!
< HTTP/2 200
< date: Tue, 20 Jul 2021 05:57:17 GMT
< content-type: application/json; charset=utf-8
< content-length: 292
< x-powered-by: Express
< x-ratelimit-limit: 1000
< x-ratelimit-remaining: 999
< x-ratelimit-reset: 1626031770
< vary: Origin, Accept-Encoding
< access-control-allow-credentials: true
< cache-control: max-age=43200
< pragma: no-cache
< expires: -1
< x-content-type-options: nosniff
< etag: W/"124-yiKdLzqO5gfBrJFrcdJ8Yq0LGnU"
< via: 1.1 vegur
< cf-cache-status: HIT
< age: 28667
< accept-ranges: bytes
< expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
< report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=gUOmBzI7k0T5EKab5qTnEmQr8zlawq5NiGkpmOgunm3BZ1uTpwzNgED2daofOGP9Mu0w%2BmxlNWqaOm34jm%2FUPQxvp%2FzADfeegmPsz7RLWIzPIr2MWbg7Swdpf6%2BKjMtEhDU0NZBUZqY6ukl5v%2BAu"}],"group":"cf-nel","max_age":604800}
< nel: {"report_to":"cf-nel","max_age":604800}
< server: cloudflare
< cf-ray: 6719f37deb9c04df-LAX
< alt-svc: h3-27=":443"; ma=86400, h3-28=":443"; ma=86400, h3-29=":443"; ma=86400, h3=":443"; ma=86400
<
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
* Connection #0 to host jsonplaceholder.typicode.com left intact
}* Closing connection 0

유용한 옵션

위에서 작성한 예제를 유심해 보시면 curl 커맨드 실행 결과 맨 뒤에 % 기호가 붙어있는 것을 볼 수 있습니다. % 기호 대신에 다른 문자를 종료 기호로 사용하고 싶다면 -w 또는 --write-out 옵션을 사용하면 됩니다.

예를 들어, 줄바꿈을 하고 싶다면..

$ curl https://jsonplaceholder.typicode.com/posts/1 -w '\n'
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}

마치면서

이상으로 curl 커맨드를 사용해서 터미널에서 HTTP 호출을 하는 기본적인 방법과 자주 사용되는 옵션에 대해서 알아보았습니다. curl 커맨드에 대한 좀 더 자세한 내용은 공식 사이트를 참고 바랍니다.