curl是一个命令行工具,通过指定的URL来上传或下载数据,并将数据展示出来。curl中的c表示client,而URL,就是URL。这里我们介绍一下curl的使用。

1. 命令行基础

1.1 命令行选项

1. 短形式

在curl中可以使用短形式的命令行选项,比如通知curl打开verbose模式,可以使用-v选项:

  1. $ curl -v www.baidu.com

这里,-v就是短形式的选项,我们使用一个中划线(-)并紧跟着一个字母来指定一个短形式的选项。
在这个例子中,-v就像一个开关一样,指定某个变量是false还是true。我们可以在一个中划线后面跟多个单字母的选项,在curl中,命令行解析器总是解析整个命令行,因此,选项可以放在整个命令行的任何位置:

$ curl -vL www.baidu.com
$ curl www.baidu.com -vL

这个和上面的命令具有同样的效果。当然,虽然是任何位置,但不能放在curl前面:

$ -vL curl www.baidu.com    // No command '-vL' found

2. 长形式

单个字母的选项敲起来和用起来都很方便,但是字母的个数有限而需要控制的东西又太多,这个时候就可以使用选项的长形式。同样,为了使得命令便于阅读,绝大多数短形式都有一个对应的长形式。
和短形式不同的是,长形式的选项使用两个中划线(—)指定,然后紧跟着具体的选项。还有就是,在使用长形式的时候,后面只能跟一个选项。对于-v,对应的长形式如下:

$ curl --verbose www.baidu.com

同样,长形式的选项也可以出现在命令的任何地方:

$ curl www.baidu.com --verbose

对于-vL来说,对应的长形式可以是:

$ curl --verbose --location www.baidu.com
$ curl --location www.baidu.com --verbose

3. 选项的参数

在上面的命令中选项-v(或--verbose)以及-L(或--location)都是bool类型的标志位,来告诉curl打开或关闭某些特征。curl还有一种类型的选项,就是需要传递一些参数。比如,如果想在一个HTTP POST中向服务器传递一个字符串:

$ curl -d arbitrary http://example.com
$ curl --data arbitrary http://example.com

4. 参数有空格?

在上面的例子中,我们的参数arbitrary是一个连续的字符串,但是当我们需要传递一个有空格的参数怎么办?比如Are you OK?,这时我们需要使用引号把参数括起来:

$ curl -A "Are you OK?" http://example.com

如果不加引号的话:

$ curl -A Are you OK? http://example.com

那么curl只会把Are当做用户的参数,剩下的字符,youOK?会被curl当做额外的URL处理,因为这里并没有用-指定这是一个选项。
但是如果参数本身有引号的时候怎么办?这在使用JSON传递参数的时候尤其常见,我们可以使用单引号把参数括起来(不过在Windows中不管用):

$ curl -d '{"name":"fool"}' http://example.com

当数据很多时,我们可以指定一个文件,来传递给curl:

$ curl -d @params.json http://example.com

5. Say No

对于像-t-L之类的标志选项,我们可以在长形式的前面加上no-前缀来指定关闭相应的特征,比如关闭verbose模式:

$ curl --no-verbose http://example.com

1.2 URL

curl支持在一个命令行中处理多个URL,中间用空格间隔即可。curl会对传进来的URL做简单的验证,而不会去验证URL是否真正有效,因此,这里需要使用者提供有效的URL。
前面说过,curl首先解析整个命令行,将得到选项应用于所有的URL上。如果想对每一个URL使用不同的选项,那么可以使用--next来指定。比如:

$ curl --location http://example.com/1 
    --next --data sendthis http://example.com/2 
    --next head http://example.com/3

1. 配置文件

如果选项过多,导致命令很难输入,或者超过了系统命令最大长度的限制,我们可以使用配置文件(config file)来指定curl的选项。
通过使用-K--config选项来告诉curl从指定的文件中读取选项,比如:

$ curl -K curl.options http://example.com

在文件curl.options中,列出所有需要的选项:

# ask curl to follow redirects
--location
# ask curl to do a HEAD request
--head

和在命令行中一样,在配置文件中也可以使用长形式或短形式,甚至在配置文件中对于长形式可以省略那两个中划线(—):

# ask curl to follow redirects
location
# ask curl to do a HEAD request
head

对于使用参数的选项,同样可以使用配置文件:

# ask curl to change the User-Agent in HTTP header
user-agent "something-is-an-agent"

既然叫做配置文件,那么上面的选项也可以写作:

# ask curl to change the User-Agent in HTTP header
user-agent = "something-is-an-agent"

甚至可以省略没有空格的参数的引号:

# ask curl to change the User-Agent in HTTP header
user-agent = something-is-an-agent

当然,如果参数中有空格的话就不能省略引号了。

2. 开始使用curl

在前面我们简单地介绍了什么是curl以及一些基础的命令行知识。我们通过命令行的方式将需要处理的URL交给curl去处理。
在这里,我们开始着手使用curl,了解curl能做什么以及如何去做。

2.1 Verbose模式

如果curl得到的结果不是期望的结果,我们可以使用-v--verbose进入Verbose模式获取更多的信息。

1. 查看通信过程

在Verbose模式中,curl会得到更多的对话式信息,帮助我们了解发生了什么。curl会在每一个信息前面加上*进行标识。在下面的例子中,我们将百度的首页保存下来(使用-o选项并指定参数baidu):

$ curl -v www.baidu.com -o baidu

我们可以得到如下的信息:

* About to connect() to www.baidu.com port 80 (#0)
*   Trying 14.215.177.39... connected
* Connected to www.baidu.com (14.215.177.39) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: www.baidu.com
> Accept: */*
>
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: Keep-Alive
< Content-Length: 2381
< Content-Type: text/html
< Date: Fri, 14 Sep 2018 09:55:18 GMT
< Etag: "588604dd-94d"
< Last-Modified: Mon, 23 Jan 2017 13:27:57 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<
{ [data not shown]
103  2381  103  2381    0     0   113k      0 --:--:-- --:--:-- --:--:--  232k* Connection #0 to host www.baidu.com left intact
* Closing connection #0

下面的信息是建立一个链接:

* About to connect() to www.baidu.com port 80 (#0)
*   Trying 14.215.177.39... connected
* Connected to www.baidu.com (14.215.177.39) port 80 (#0)

然后就是本次的HTTP请求:

> GET / HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: www.baidu.com
> Accept: */*
>

接下来是数据的传输过程。然后就是响应:

< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: Keep-Alive
< Content-Length: 2381
< Content-Type: text/html
< Date: Fri, 14 Sep 2018 09:55:18 GMT
< Etag: "588604dd-94d"
< Last-Modified: Mon, 23 Jan 2017 13:27:57 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<

连箭头都不一样了。

2. 更详细的信息

如果觉得使用-v的信息还不够的话,还可以使用--trace-ascii [filename]选项来讲完整的流保存到filename中。比如:

$ curl --trace-ascii dump www.baidu.com

之后,就可以发现一个新的文件dump,里面保存着刚才那个会话的所有信息:

== Info:   Trying 61.135.169.125...
== Info: TCP_NODELAY set
== Info: Connected to www.baidu.com (61.135.169.125) port 80 (#0)
=> Send header, 77 bytes (0x4d)
0000: GET / HTTP/1.1
0010: Host: www.baidu.com
0025: User-Agent: curl/7.64.1
003e: Accept: */*
004b: 
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 22 bytes (0x16)
0000: Accept-Ranges: bytes
<= Recv header, 76 bytes (0x4c)
0000: Cache-Control: private, no-cache, no-store, proxy-revalidate, no
0040: -transform
<= Recv header, 24 bytes (0x18)
0000: Connection: keep-alive
<= Recv header, 22 bytes (0x16)
0000: Content-Length: 2381
<= Recv header, 25 bytes (0x19)
0000: Content-Type: text/html

文件的前22行如上所示。每一个发送和接收的数据以ASCII码的形式保存起来了,方便以后的分析。如果觉得想要查看十六进制的话,可以使用--trace [filename]选项。

3. Silence

verbose模式的对立模式,就是silence,可以使用-s--silence选项来告诉curl不输出任何程序的信息或者错误信息,但也会输出响应的结果。
如果需要在有错误的时候输出错误信息,可以使用-S--show-error来指定。

2.2 浏览器到curl

别人使用浏览器发起了一个请求之后,如果自己想用curl再来一次同样的请求,这里日常的工作中是比较常见的一个操作。在curl中,有没有什么比较简便快捷的方式来获得curl命令呢?
Chrome浏览器和Firefox浏览器都实现了复制成curl的工具,可以将浏览器的请求快速复制成curl命令,非常方便快捷。

1. Chrome

在Chrome中,打开More tools->Developer模式,选择Network选项卡,然后就可以看到所有的请求,选中相应的请求,右键就有Copy as cURL选项,单击就可以了。
image.png

2. Firefox

在Firefox中,打开Web Developer->Network工具,然后右键想要复制的链接,就有一个Copy as cURL的选项,单击就可以了。

3. HTTP与curl

与curl一起使用的协议,最多的还是HTTP,这里就将介绍如何有效地使用curl来发送HTTP请求。

3.1 HTTP方法

在每一个HTTP请求中,都有一个对应的方法,常用的方法有:GETPOSTHEADPUT,默认的使用GET方法。对于其它的方法,可以在curl命令中指定:

method option
POST -d-F
HEAD -I
PUT -T

3.2 Header

在curl中,使用-i选项可以显示Response的Header信息,连同Body数据:

$ curl -i www.baidu.com

结果:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
Connection: keep-alive
Content-Length: 2381
Content-Type: text/html
Date: Mon, 02 Mar 2020 02:25:22 GMT
Etag: "588604c1-94d"
Last-Modified: Mon, 23 Jan 2017 13:27:29 GMT
Pragma: no-cache
Server: bfe/1.0.8.18
Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/

<!DOCTYPE html>
<!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8>
<meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer>
<link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css>
<title>百度一下,你就知道</title></head> <body link=#0000cc> .... </body> </html>

使用-I选项可以只显示Response的Header信息:

$ curl -I www.baidu.com

3.3 POST

POST是HTTP中向服务端提交数据的一种方法。在浏览器中,但在表单中填写完数据后,浏览器就会默认将填写的数据使用key=value串的形式进行转化。在curl中,我们可以使用-d--data选项来指定具体的数据:

$ curl -d key1=value1&key2=value2 http://example.com

我们也可以使用多个-d选项来指定多组数据,curl会自动把这些数据连接起来,因此上面的例子还可以这样:

$ curl -d key1=value1 -d key2=value2 http://example.com

当然,如果数据过多,我们还可以把数据放在一个文件中:

$ curl -d @filename http://example.com

1. Content-Type

当使用POST方法提交数据时,对于提交的数据主要有如下四种形式:

  • application/x-www-form-urlencoded:默认的形式,即key1=value1&key2=value2的形式;
  • multipart/form-data:使用表单上传文件时使用这个形式;
  • application/json:提交JSON格式的数据;
  • text/xml:提交XML格式的数据。

Content-Type是一个Header,如果不指定的话,那么默认就是使用application/x-www-form-urlencoded形式传输数据,当需要使用别的形式进行数据传输的话,那么就需要指定这个Header:

$ curl -d '{I Am A JSON FORM}' -H 'Content-Type: application/json' http://example.com

其中,-H就是用来指定一个具体的Header的选项,值就是key=value 的形式。当需要指定其它的Header,可以使用-H选项。

2. POST一个二进制数据

在curl中,我们也可以提交一个文件,可以使用--data-binary选项来指定一个文件:

$ curl --data-binary @filename http://example.com

3. 转化成一个GET

使用-G-get选项,可以把一个POST请求转化成一个GET请求。如果有-d选项指定的参数,那么curl就会把-d后面的数据添加到URL的后面,用?连接。比如:

$ curl -d "key1=value1" -G http://example.com

得到的请求URL就是:

http://example.com/?key1=value1

4. URL编码

如果使用的数据没有编码,那么可以指定curl来帮助自己进行编码。这时可以使用--data-urlencode选项来指定。比如:

$ curl --data-urlencode "name=Alan Walker" http://example.com

5. multipart formposts

如果一个HTTP POST具有如下形式的表单:

<form action="submit.cgi" method="post" enctype="multipart/form-data">
    Name: <input type="text" name="person"><br>
    File: <input type="file" name="secret"><br>
    <input type="submit" value="Submit">
</form>

用户可以在Name中填写名字,在File中选择一个文件,然后单击Submit按钮提交数据。为了可以在curl中模拟这个请求,我们可以使用-F--form选项来指定数据:

$ curl -F person=annonymous -F secret=@filename http://example.com/submit.cgi

在上面的表单中,action指定了这个请求发送到哪里;method指定这是一个POST请求;而enctype指定了这是一个multipart formpost。当执行上面的curl命令后,curl会产生如下的请求头:

POST /submit.cgi HTTP/1.1
Host: example.com
User-Agent: curl/7.46.0
Accept: */*
Content-Length: 313
Content-Type: multipart/form-data; boundary=------------------------d74496d66958873e

其中Content-Type是和enctype一致的。

当使用-F选项时,默认的Content-Type就是multipart/form-data,不过,我们也可以使用-H进行指定:

$ curl -F 'name=Dan' -H 'Content-Type: multipart/magic' https://example.com

6. -d vs -F

在前面我们介绍了使用-d构造一个基本的POST请求,和-F构造一个multipart formpost请求。那么这两个选项有啥区别以及什么时候使用呢?
这两个选项都是把指定的数据发送到服务器上,区别在于数据传递的格式。大多数时候,接收端来指定希望客户端发送数据的格式,客户端不能随意自己指定格式。

  • HTML表单

当使用HTML表单时,会使用<form>标签指定一个表单,这会让浏览器使用POST方法。如果标签中含有enctype=multipart/form-data,这意味着使用multipart formpost方式,在curl中就是使用-F选项。一个典型的场景就是表单中含有<input type=file>标签。

  • 不用HTML表单

POST方法不一定非要在HTML中,在好多的service、APIs中,也可以使用POST请求。
如果这些service期望使用JSON或者其它类似的格式的数据,那么这就是一个普通的POST请求。在curl中就可以使用-d选项。不过要注意-d的默认Content-Type是不是期望的格式,如果不是的话,可以使用-H进行更改。

3.4 HTTP重定向(redirect)

重定向是HTTP协议中的一个基础部分。在重定向中,服务器给客户端的并不是客户端想要的内容,而是一个具体的指令,告诉客户端如果想获取想要的数据,应该到哪里去请求。
但不是所有的重定向都一样。重定向之后的请求使用什么方法呢?重定向多久呢?
所有的重定向都会返回Location:的Header,来指定一个新的URL。

1. curl:redirect

在curl中,默认不会重定向,可以使用-L--location选项来告诉curl重定向:

$ curl -L http://example.com

2. GET还是POST

第一次请求后,服务器会告诉客户端下一次请求需要使用的方法。关于重定向的响应码如下:

Method Permanent Temporary
切换到GET 301 302和303
使用第一次请求的方法 308 307

我们可以指定curl在重定向时使用什么方法。如果我们第一次请求使用的不是GET方法,重定向后也不希望curl默认使用GET方法,那么我们可以使用--post301,--post302--post303选项来指定。

3.5 修改HTTP请求

每一个请求都有一个请求行、一些请求头和可选的请求体,这里我们看看在curl中可以具体修改的部分,包括请求行和请求头。

1. 请求方法

在请求行中包含这次请求所使用的方法。我们使用下面的简单命令就可以进行一个GET方法:

$ curl http://example.com/file

这会生成如下的请求行:

GET /file HTTP/1.1

HTTP方法中我们可以通过具体的选项指定使用什么方法。这里我们也可以使用-X选项来进行指定:

$ curl -X POST http://example.com

2. 修改请求头

在curl中,我们可以使用-H--header选项来指定Header。之前我们就使用-H指定了Content-Type,其实Header就是一个key: value对:

$ curl -H "HeaderName: HeaderValue" http://example.com

3. Referer

我们还可以在curl通过--referer选项来指定我们是从哪里跳转过来的:

$ curl --referer http://fromexample.com http://toexample.com

4. User Agent

这个字段是用来表示客户端的设备信息的,服务器会根据这个字段,针对不同的设备,返回不同格式的网页。在curl中,可以使用--user-agent选的来指定:

$ curl --user-agent "[User Agent]" http://example.com

3.6 Cookies

HTTP是一种无状态的协议,为了在会话中保存一些状态,可以使用Cookies。服务器通过Set-Cookie:来设置Cookie,客户端就可以在下一次请求中携带这些数据。

1. 设置Cookie

我们可以使用--cookie选项来设置一个Cookie:

$ curl --cookie "CookieName=CookieValue" http://example.com

2. 从文件中读取Cookies

curl默认不会记住服务器设置的Cookie,也不会在下一次请求中携带Cookie。除非用户通过选项自己设置。我们可以把之前的Cookies保存到一个文件,然后在下一次请求中指定curl读取文件中的Cookies:

$ curl -b cookies.txt http://example.com

-b选项指定curl去给定的文件中读取Cookies。
不过要主要,这里仅仅是读取Cookies,如果这次请求中服务器修改了Cookie,那么curl是不会进行保存的,除非我们手动指定。

3. 写Cookies到文件

我们可以使用-c选项指定curl保存这次请求中服务器设置的Cookies:

$ curl -c cookie.jar.txt http://example.com

有时,我们既需要从文件中读取Cookies,也需要保存服务器设置的Cookies。那么就可以同时使用-b-c选项:

$ curl -b cookies.txt -c cookie.jar.txt http://example.com