WIFI学习二(HTTP介绍)
HTTP是Hypertext Transfer Protocol(超文本传输协议)的缩写。超文本传输协议是一种用于分布式、协作式和超媒体信息系统的应用层协议,是因特网上应用最为广泛的一种网络传输协议,所有的 WWW 文件都必须遵守这个标准。...
简介
HTTP是Hypertext Transfer Protocol(超文本传输协议)的缩写。超文本传输协议是一种用于分布式、协作式和超媒体信息系统的应用层协议,是因特网上应用最为广泛的一种网络传输协议,所有的 WWW 文件都必须遵守这个标准。
HTTP 是为客户端与服务器之间的通信而设计的,但也可以用于其他目的。
HTTP 是一个基于 TCP/IP 通信协议来传递数据的(HTML 文件、图片文件、查询结果等)。
HTTP协议的请求及响应方式设计如下:
从图中可以看出,服务器端响应客户端请求后立刻断开连接,连接不会维持很久,即使同一个客户端再次发送请求,服务端也无法辨认出是否是原先的那个客户端发出的请求,会以相同的方式处理新的请求。
HTTP三点注意事项:
HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。
HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。
HTTP请求
HTTP请求是客户端向服务端发送请求消息,请求消息可以分为请求行(request line)、消息头(header)、消息体(message)三个部分.
请求行由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。例如,GET /index.html HTTP/1.1。
根据HTTP标准,HTTP请求可以使用多种请求方法。
HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
目前大部分只支持GET和POST方法。GET用于请求数据,POST主要用于传输数据;
消息头包括一些访问的域名、用户代理、Cookie等信息;
Header | 解释 | 示例 |
Accept | 指定客户端能够接收的内容类型 | Accept: text/plain, text/html,*/* |
Accept-Charset | 浏览器可以接受的字符编码集。 | Accept-Charset: iso-8859-5 |
Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型。 | Accept-Encoding: compress, gzip |
Accept-Language | 浏览器可接受的语言 | Accept-Language: en,zh |
Accept-Ranges | 可以请求网页实体的一个或者多个子范围字段 | Accept-Ranges: bytes |
Authorization | HTTP授权的授权证书 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache |
Connection | 表示是否需要持久连接。(HTTP 1.1默认进行持久连接) | Connection: keep-alive |
Cookie | HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器。 | Cookie: $Version=1; Skin=new; |
Content-Length | 请求的内容长度 | Content-Length: 348 |
Content-Type | 请求的与实体对应的MIME信息 | Content-Type: application/x-www-form-urlencoded |
Date | 请求发送的日期和时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Expect | 请求的特定的服务器行为 | Expect: 100-continue |
From | 发出请求的用户的Email | From: user@email.com |
Host | 指定请求的服务器的域名和端口号 | Host: www.zcmhi.com |
If-Match | 只有请求内容与实体相匹配才有效 | If-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Modified-Since | 如果请求的部分在指定时间之后被修改则请求成功,未被修改则返回304代码 | If-Modified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
If-None-Match | 如果内容未改变返回304代码,参数为服务器先前发送的Etag,与服务器回应的Etag比较判断是否改变 | If-None-Match: “737060cd8c284d8af7ad3082f209582d” |
If-Range | 如果实体未改变,服务器发送客户端丢失的部分,否则发送整个实体。参数也为Etag | If-Range: “737060cd8c284d8af7ad3082f209582d” |
If-Unmodified-Since | 只在实体在指定时间之后未被修改才请求成功 | If-Unmodified-Since: Sat, 29 Oct 2010 19:43:31 GMT |
Max-Forwards | 限制信息通过代理和网关传送的时间 | Max-Forwards: 10 |
Pragma | 用来包含实现特定的指令 | Pragma: no-cache |
Proxy-Authorization | 连接到代理的授权证书 | Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Range | 只请求实体的一部分,指定范围 | Range: bytes=500-999 |
Referer | 先前网页的地址,当前请求网页紧随其后,即来路 | Referer: http://www.zcmhi.com/archives/71.html |
TE | 客户端愿意接受的传输编码,并通知服务器接受接受尾加头信息 | TE: trailers,deflate;q=0.5 |
Upgrade | 向服务器指定某种传输协议以便服务器进行转换(如果支持) | Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11 |
User-Agent | User-Agent的内容包含发出请求的用户信息 | User-Agent: Mozilla/5.0 (Linux; X11) |
Via | 通知中间网关或代理服务器地址,通信协议 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 关于消息实体的警告信息 | Warn: 199 Miscellaneous warning |
消息体就是请求的数据,仅在POST方式请求时候输入。
HTTP响应
HTTP响应是指服务端根据客户端发送的请求中的动作要求做出具体的动作,然后将结果返回给客户端。 HTTP响应消息可以分为状态行、头信息、消息体三个部分;状态行含有请求的状态信息,这是其与请求消息相比最大的区别。
响应的头信息包括如下:
Header | 解释 | 示例 |
Accept-Ranges | 表明服务器是否支持指定范围请求及哪种类型的分段请求 | Accept-Ranges: bytes |
Age | 从原始服务器到代理缓存形成的估算时间(以秒计,非负) | Age: 12 |
Allow | 对某网络资源的有效的请求行为,不允许则返回405 | Allow: GET, HEAD |
Cache-Control | 告诉所有的缓存机制是否可以缓存及哪种类型 | Cache-Control: no-cache |
Content-Encoding | web服务器支持的返回内容压缩编码类型。 | Content-Encoding: gzip |
Content-Language | 响应体的语言 | Content-Language: en,zh |
Content-Length | 响应体的长度 | Content-Length: 348 |
Content-Location | 请求资源可替代的备用的另一地址 | Content-Location: /index.htm |
Content-MD5 | 返回资源的MD5校验值 | Content-MD5: Q2hlY2sgSW50ZWdyaXR5IQ== |
Content-Range | 在整个返回体中本部分的字节位置 | Content-Range: bytes 21010-47021/47022 |
Content-Type | 返回内容的MIME类型 | Content-Type: text/html; charset=utf-8 |
Date | 原始服务器消息发出的时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
ETag | 请求变量的实体标签的当前值 | ETag: “737060cd8c284d8af7ad3082f209582d” |
Expires | 响应过期的日期和时间 | Expires: Thu, 01 Dec 2010 16:00:00 GMT |
Last-Modified | 请求资源的最后修改时间 | Last-Modified: Tue, 15 Nov 2010 12:45:26 GMT |
Location | 用来重定向接收方到非请求URL的位置来完成请求或标识新的资源 | Location: http://www.zcmhi.com/archives/94.html |
Pragma | 包括实现特定的指令,它可应用到响应链上的任何接收方 | Pragma: no-cache |
Proxy-Authenticate | 它指出认证方案和可应用到代理的该URL上的参数 | Proxy-Authenticate: Basic |
refresh | 应用于重定向或一个新的资源被创造,在5秒之后重定向(由网景提出,被大部分浏览器支持) | Refresh: 5; url=http://www.zcmhi.com/archives/94.html |
Retry-After | 如果实体暂时不可取,通知客户端在指定时间之后再次尝试 | Retry-After: 120 |
Server | web服务器软件名称 | Server: Apache/1.3.27 (Unix) (Red-Hat/Linux) |
Set-Cookie | 设置Http Cookie | Set-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1 |
Trailer | 指出头域在分块传输编码的尾部存在 | Trailer: Max-Forwards |
Transfer-Encoding | 文件传输编码 | Transfer-Encoding:chunked |
Vary | 告诉下游代理是使用缓存响应还是从原始服务器请求 | Vary: * |
Via | 告知代理客户端响应是通过哪里发送的 | Via: 1.0 fred, 1.1 nowhere.com (Apache/1.1) |
Warning | 警告实体可能存在的问题 | Warning: 199 Miscellaneous warning |
WWW-Authenticate | 表明客户端请求实体应该使用的授权方案 | WWW-Authenticate: Basic |
相关函数介绍
http_get() 从服务器中读取数据
GET方法要求服务器将URL定位的资源放在响应报文的数据部分,回送给客户端。
函数原型
HTTPCLIENT_RESULT httpclient_get(httpclient_t *client, char *url, httpclient_data_t *client_data);
参数:
Client:
指向http_client的指针
typedef struct {
int socket; /**< Socket ID. */
int remote_port; /**< HTTP or HTTPS port. */
int response_code; /**< Response code. */
char *header; /**< Request custom header. */
char *auth_user; /**< Username for basic authentication. */
char *auth_password; /**< Password for basic authentication. */
bool is_http; /**< Http connection? if 1, http; if 0, https. */
#ifdef MTK_HTTPCLIENT_SSL_ENABLE
const char *server_cert; /**< Server certification. */
const char *client_cert; /**< Client certification. */
const char *client_pk; /**< Client private key. */
int server_cert_len; /**< Server certification lenght, server_cert buffer size. */
int client_cert_len; /**< Client certification lenght, client_cert buffer size. */
int client_pk_len; /**< Client private key lenght, client_pk buffer size. */
void *ssl; /**< Ssl content. */
#endif
#ifdef HTTPCLIENT_REDIRECT
int redirect_times; /**< Current redirection times. */
#endif
} httpclient_t;
Url:
就是平时所说的“网址”
client_data:
/** @brief This structure defines the HTTP data structure. */
typedef struct {
bool is_more; /**< Indicates if more data needs to be retrieved. */
bool is_chunked; /**< Response data is encoded in portions/chunks.*/
int retrieve_len; /**< Content length to be retrieved. */
int response_content_len; /**< Response content length. */
int content_block_len; /**< The content length of one block. */
int post_buf_len; /**< Post data length. */
int response_buf_len; /**< Response body buffer length. */
int header_buf_len; /**< Response head buffer lehgth. */
char *post_content_type; /**< Content type of the post data. */
char *post_buf; /**< User data to be posted. */
char *response_buf; /**< Buffer to store the response body data. */
char *header_buf; /**< Buffer to store the response head data. */
// Range Feature
int range_enable; /**< Indicates use range request or not. */
int range_begin; /**< The source begin if range_enable is 1. */
int range_end; /**< The source end if range_enable is 1. */
int range_len; /**< The source length if range_enable is 1. */
} httpclient_data_t;
该参数中需要关注的是response_buf和response_buf_len参数。因为需要获取Http数据,所以参数需要有接受数据的缓冲区以及长度。
返回值:
/** @brief This enumeration defines the API return type. */
typedef enum {
HTTPCLIENT_ERROR_PARSE = -6, /**< A URL parse error occurred. */
HTTPCLIENT_UNRESOLVED_DNS = -5, /**< Could not resolve the hostname. */
HTTPCLIENT_ERROR_PRTCL = -4, /**< A protocol error occurred. */
HTTPCLIENT_ERROR = -3, /**< An unknown error occurred. */
HTTPCLIENT_CLOSED = -2, /**< Connection was closed by a remote host. */
HTTPCLIENT_ERROR_CONN = -1, /**< Connection failed. */
HTTPCLIENT_OK = 0, /**< The operation was successful. */
HTTPCLIENT_RETRIEVE_MORE_DATA = 1 /**< More data needs to be retrieved. */
} HTTPCLIENT_RESULT;
实例:
char *url = "https://www.baidu.com/";
httpclient_t client = {0};
httpclient_data_t client_data = {0};
char *buf = NULL;
buf = pvPortMalloc(BUF_SIZE);
if (buf == NULL) {
printf("Malloc failed.\r\n");
return;
}
memset(buf, 0, sizeof(buf));
client_data.response_buf = buf; //Sets a buffer to store the result.
client_data.response_buf_len = BUF_SIZE; //Sets the buffer size.
httpclient_get(&client, url, &client_data);
printf("Data received: %s\r\n", client_data.response_buf);
http_post() 推送数据到服务器
该函数向指定资源提交数据进行处理。
函数原型:
HTTPCLIENT_RESULT httpclient_post(httpclient_t *client, char *url, httpclient_data_t *client_data);
参数:
Client:
同上
Url:
网址
client_data:
这里需要注意char *post_content_type参数
Content-type的类型
常见的媒体格式类型:
text/html : HTML格式
text/plain :纯文本格式
text/xml : XML格式
image/gif :gif图片格式
image/jpeg :jpg图片格式
image/png:png图片格式
以applicaton开头的媒体类型:
application/json : JSON数据格式
application/xhtml+xml :XHTML格式
application/xml : XML数据格式
application/atom+xml :Atom XML聚合格式
application/pdf :pdf格式
application/javascript :js格式
application/msword : Word文档格式
application/octet-stream : 二进制流数据(如常见的文件下载)
application/x-www-form-urlencoded :form表单默认的数据格式类型,form表单数据被编码为key/value格式发送到服务器。
返回值:
/** @brief This enumeration defines the API return type. */
typedef enum {
HTTPCLIENT_ERROR_PARSE = -6, /**< A URL parse error occurred. */
HTTPCLIENT_UNRESOLVED_DNS = -5, /**< Could not resolve the hostname. */
HTTPCLIENT_ERROR_PRTCL = -4, /**< A protocol error occurred. */
HTTPCLIENT_ERROR = -3, /**< An unknown error occurred. */
HTTPCLIENT_CLOSED = -2, /**< Connection was closed by a remote host. */
HTTPCLIENT_ERROR_CONN = -1, /**< Connection failed. */
HTTPCLIENT_OK = 0, /**< The operation was successful. */
HTTPCLIENT_RETRIEVE_MORE_DATA = 1 /**< More data needs to be retrieved. */
} HTTPCLIENT_RESULT;
实例:
char *url = "https://api.mediatek.com/mcs/v2/devices/D0n2yhrl/datapoints.csv";
char *header = "deviceKey:FZoo0S07CpwUHcrt\r\n";
char *content_type = "text/csv";
char *post_data = "1,,I am string!";
httpclient_t client = {0};
httpclient_data_t client_data = {0};
char *buf = NULL;
buf = pvPortMalloc(BUF_SIZE);
if (buf == NULL) {
printf("Malloc failed.\r\n");
return;
}
memset(buf, 0, sizeof(buf));
client_data.response_buf = buf; //Sets a buffer to store the result.
client_data.response_buf_len = BUF_SIZE; //Sets the buffer size.
httpclient_set_custom_header(&client, header); //Sets the custom header if needed.
client_data.post_buf = post_data; //Sets the user data to be posted.
client_data.post_buf_len = strlen(post_data); //Sets the post data length.
client_data.post_content_type = content_type; //Sets the content type.
httpclient_post(&client, url, &client_data);
printf("Data received: %s\r\n", client_data.response_buf);
这里httpclient_set_custom_header(&client, header)函数是配置请求头,通常用于http的请求和鉴权。
注:POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因为(1)登录页面有可能被浏览器缓存, (2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了。而通过POST提交数据是经过加密的。
httpclient_connect()
与远程的URL建立一个HTTP连接
函数原型
HTTPCLIENT_RESULT httpclient_connect(httpclient_t *client, char *url);
参数:
client:
HTTP客户端描述符,当与远程URL建立链接成功后,作为参数被其他函数调用。结构体原型如下:
/** @brief This structure defines the httpclient_t structure. */
typedef struct {
int socket; /**< Socket ID. */
int remote_port; /**< HTTP or HTTPS port. */
int response_code; /**< Response code. */
char *header; /**< Request custom header. */
char *auth_user; /**< Username for basic authentication. */
char *auth_password; /**< Password for basic authentication. */
bool is_http; /**< Http connection? if 1, http; if 0, https. */
#ifdef MTK_HTTPCLIENT_SSL_ENABLE
const char *server_cert; /**< Server certification. */
const char *client_cert; /**< Client certification. */
const char *client_pk; /**< Client private key. */
int server_cert_len; /**< Server certification lenght, server_cert buffer size. */
int client_cert_len; /**< Client certification lenght, client_cert buffer size. */
int client_pk_len; /**< Client private key lenght, client_pk buffer size. */
void *ssl; /**< Ssl content. */
#endif
#ifdef HTTPCLIENT_REDIRECT
int redirect_times; /**< Current redirection times. */
#endif
} httpclient_t;
url:
需要建立HTTP连接的远程服务器域名
返回值:
typedef enum {
HTTPCLIENT_ERROR_PARSE = -6, /**< A URL parse error occurred. */
HTTPCLIENT_UNRESOLVED_DNS = -5, /**< Could not resolve the hostname. */
HTTPCLIENT_ERROR_PRTCL = -4, /**< A protocol error occurred. */
HTTPCLIENT_ERROR = -3, /**< An unknown error occurred. */
HTTPCLIENT_CLOSED = -2, /**< Connection was closed by a remote host. */
HTTPCLIENT_ERROR_CONN = -1, /**< Connection failed. */
HTTPCLIENT_OK = 0, /**< The operation was successful. */
HTTPCLIENT_RETRIEVE_MORE_DATA = 1 /**< More data needs to be retrieved. */
} HTTPCLIENT_RESULT;
成功时返回0,其余值对应错误类型。
实例:
#define HTTPS_MTK_CLOUD_POST_URL "https://api.mediatek.com/mcs/v2/devices/D0n2yhrl/datapoints.csv"
httpclient_t client = {0};
char *post_url = HTTPS_MTK_CLOUD_POST_URL;
HTTPCLIENT_RESULT ret = HTTPCLIENT_ERROR_CONN;
ret = httpclient_connect(&client, post_url);
if (ret < 0)
goto fail;
httpclient_send_request()
向远程服务器发送HTTP请求(GET或POST)。
函数原型:
HTTPCLIENT_RESULT httpclient_send_request(httpclient_t *client, char *url, int method, httpclient_data_t *client_data);
参数:
client,客户端描述符
httpclient_connect()函数建立连接时的客户端描述符。
url,远程URL
method,方式,类型如下:
typedef enum {
HTTPCLIENT_GET,
HTTPCLIENT_POST,
HTTPCLIENT_PUT,
HTTPCLIENT_DELETE,
HTTPCLIENT_HEAD
} HTTPCLIENT_REQUEST_TYPE;
client_data,数据缓冲区,结构体原型如下:
typedef struct {
bool is_more; /**< Indicates if more data needs to be retrieved. */
bool is_chunked; /**< Response data is encoded in portions/chunks.*/
int retrieve_len; /**< 某次请求剩余数据长度Content length to be retrieved. */
int response_content_len; /**< Response content length. */
int content_block_len; /**< The content length of one block. */
int post_buf_len; /**< Post data length. */
int response_buf_len; /**< Response body buffer length. */
int header_buf_len; /**< Response head buffer lehgth. */
char *post_content_type; /**< Content type of the post data. */
char *post_buf; /**< User data to be posted. */
char *response_buf; /**< Buffer to store the response body data. */
char *header_buf; /**< Buffer to store the response head data. */
// Range Feature
int range_enable; /**< Indicates use range request or not. */
int range_begin; /**< The source begin if range_enable is 1. */
int range_end; /**< The source end if range_enable is 1. */
int range_len; /**< The source length if range_enable is 1. */
} httpclient_data_t;
每次的GET或POST之前,都需要配置好client_data。配置好后,后续的GET或POST操作,结果会直接到client_data中。
返回值:
typedef enum {
HTTPCLIENT_ERROR_PARSE = -6, /**< A URL parse error occurred. */
HTTPCLIENT_UNRESOLVED_DNS = -5, /**< Could not resolve the hostname. */
HTTPCLIENT_ERROR_PRTCL = -4, /**< A protocol error occurred. */
HTTPCLIENT_ERROR = -3, /**< An unknown error occurred. */
HTTPCLIENT_CLOSED = -2, /**< Connection was closed by a remote host. */
HTTPCLIENT_ERROR_CONN = -1, /**< Connection failed. */
HTTPCLIENT_OK = 0, /**< The operation was successful. */
HTTPCLIENT_RETRIEVE_MORE_DATA = 1 /**< More data needs to be retrieved. */
} HTTPCLIENT_RESULT;
成功时返回0,其余值对应错误类型。
实例:
#define HTTP_GET_URL "http://www.aliyun.com/"
char *get_url = HTTP_GET_URL;
HTTPCLIENT_RESULT ret;
httpclient_t client = {0};
char *buf;
httpclient_data_t client_data = {0};
buf = pvPortMalloc(BUF_SIZE);
client_data.response_buf = buf;
client_data.response_buf_len = 128;
ret = httpclient_send_request(&client, get_url, HTTPCLIENT_GET, &client_data);
if (ret < 0)
goto fail;
httpclient_recv_response()
接收上次请求的响应。
函数原型:
HTTPCLIENT_RESULT httpclient_recv_response(httpclient_t *client, httpclient_data_t *client_data);
参数:
client,客户端描述符
httpclient_connect()函数建立连接时的客户端描述符。
client_data,数据缓冲区。
返回值:
typedef enum {
HTTPCLIENT_ERROR_PARSE = -6, /**< A URL parse error occurred. */
HTTPCLIENT_UNRESOLVED_DNS = -5, /**< Could not resolve the hostname. */
HTTPCLIENT_ERROR_PRTCL = -4, /**< A protocol error occurred. */
HTTPCLIENT_ERROR = -3, /**< An unknown error occurred. */
HTTPCLIENT_CLOSED = -2, /**< Connection was closed by a remote host. */
HTTPCLIENT_ERROR_CONN = -1, /**< Connection failed. */
HTTPCLIENT_OK = 0, /**< The operation was successful. */
HTTPCLIENT_RETRIEVE_MORE_DATA = 1 /**< More data needs to be retrieved. */
} HTTPCLIENT_RESULT;
成功时返回0,其余值对应错误类型。
实例:
#define HTTP_GET_URL "http://www.aliyun.com/"
char *get_url = HTTP_GET_URL;
HTTPCLIENT_RESULT ret;
httpclient_t client = {0};
char *buf;
httpclient_data_t client_data = {0};
buf = pvPortMalloc(BUF_SIZE);
client_data.response_buf = buf;
client_data.response_buf_len = 128;
do {
// Receive response from server
ret = httpclient_recv_response(&client, &client_data);
if (ret < 0)
goto fail;
count += strlen(client_data.response_buf);
printf("data received: %s", client_data.response_buf);
} while (ret == HTTPCLIENT_RETRIEVE_MORE_DATA);
httpclient_close()
关闭HTTP连接。
函数原型:
void httpclient_close(httpclient_t *client);
参数:
client,客户端描述符
httpclient_connect()函数建立连接时的客户端描述符。
返回值:
无
实例:
httpclient_t client = {0};
httpclient_close(&client);
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)