知识巩固源码落实之4:http get同步请求数据demo

简介: 知识巩固源码落实之4:http get同步请求数据demo

1:背景介绍

http是基于C/S模式的,一直对如何实现http请求很疑惑。

http的协议理论了解很多,却一直无从下手不知道怎么实现一个http的请求。

作为一个http的客户端,请求服务器的一个消息,实现demo,做代码备份。

1.1:协议简单了解

这里实现一个http的get请求,获取对端服务器的响应进行打印,实现demo

//http请求实际就是按照http协议,构造特定的数据,使用tcp进行发送 
//请求行 请求头部 空行  请求数据
    // GET %s %s\r\n   resource  HTTP_VERSION   请求行
  // Host: %s\r\n    hostname         请求头
  // %s\r\n          CONNECTION_TYPE      请求头
  // \r\n

1.2:关注细节

1.2.1:需要关注hostname转ip的相关方法

把hostname转为目的服务器的具体ip,创建tcp client,进行连接,发送数据得请求。

//转换 url和ip  返回ip
char *get_ip_by_host(const char * hostname)
{
  //通过域名获取ip
  struct hostent *host_entry = gethostbyname(hostname);
  if(host_entry == NULL)
  {
    return NULL;
  }
  //通过 struct hostent * 可以获取到域名  ip列表等信息
  printf("host name is [%s] \n", host_entry->h_name);
  printf("the other name is :\n");
  for(int i=0; host_entry->h_aliases[i]; ++i)
  {
    printf("   [i:%d][%s] \n", i, host_entry->h_aliases[i]);
  }
  printf("Address type: %s\n", (host_entry->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6");
  printf("address length: %d \n", host_entry->h_length);
  printf("ip list is :\n");
  for(int i=0; host_entry->h_addr_list[i]; ++i)
  {
    // printf("   [i:%d][%s] \n", i, host_entry->h_addr_list[i]);
    printf("   [i:%d][%s] \n", i, inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[i]));
  }
  return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
}

1.2.2:需要关注如何按照http协议构造要发送的数据

构造发送的字符串,使用tcp进行发送。

#define HTTP_VERSION    "HTTP/1.1"
//好像不同的浏览器有不同的一些参数  可以在网页用f12请求进行查看 
#define USER_AGENT      "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\n"
#define ENCODE_TYPE     "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
#define CONNECTION_TYPE "Connection: close\r\n"
int len = sprintf(send_buff, "GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",\
    resource, HTTP_VERSION, hostname, CONNECTION_TYPE);
//send_buff 即为我们对服务器请求的http格式的消息,使用tcp进行发送

2:测试代码

http基于tcp/ip实现的,使用tcp发送特定协议的报文数据而已。

注意按照http的协议格式构造tcp待发送数据,等待对端的回应。 (作为http的客户端,对服务器进行数据请求)

/********************************************************
info: 作为http的客户端,拉取一下远端服务器数据,练习一下
data: 2022/02/10
author: hlp
测试:./XXX www.baidu.com /
    ./XXX api.seniverse.com /v3/weather/now.json?key=0pyd8z7jouficcil&location=beijing&language=zh-Hans&unit=c
********************************************************/
//练习http的get请求,通过url转换ip,创建tcp,发送构造的http协议,请求服务器的内容
//异步发送和接收  其实是利用了epoll event的指针,指向的函数指针
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>       /* close */
#include <netdb.h> 
#include <time.h>
#include <fcntl.h>
#include <errno.h>
//转换 url和ip  返回ip
char *get_ip_by_host(const char * hostname);
//创建socket 根据服务器ip和断开 进行连接 返回fd
int create_http_socket(const char * ip);
//发送http请求 返回发送成功还是失败
int send_http_request(int fd, const char * hostname, const char* resource);
//获取回应消息并打印 监听对端的回应进行打印 返回成功或者失败
int get_recv_http_response(int sockfd);
//作为http的客户端,一请求一结束
int main(int argc, char *argv[])
{
  if(argc < 3)
  {
    printf(" please use ./%s hostname resouse \n\n", argv[0]);
    return -1;
  }
  //根据入参,解析获取到的url,保存请求的资源
  char * ip = get_ip_by_host(argv[1]);
  printf("get ip is [%s] \n", ip);
  //构造tcp连接 作为tcp的客户端,主动连接目标服务器
  int connfd = create_http_socket(ip);
  //构造http报文,发送http请求,并获取到返回结果
  send_http_request(connfd, argv[1], argv[2]);
  //监听获取http的请求的回复 内部做输出查看
  get_recv_http_response(connfd);
  close(connfd); //客户端主动关闭fd
  return 0;
}
// struct hostent {
//    char  *h_name;            /* 官方域名 */
//    char **h_aliases;         /* 别名 */
//    int    h_addrtype;        /* 地址簇类型 标明ipv4和ipv6 */
//    int    h_length;          /* ip地址的长度 */
//    char **h_addr_list;       /* 以整数的形式标识ip */
//            }
//转换 url和ip  返回ip
char *get_ip_by_host(const char * hostname)
{
  //通过域名获取ip
  struct hostent *host_entry = gethostbyname(hostname);
  if(host_entry == NULL)
  {
    return NULL;
  }
  //通过 struct hostent * 可以获取到域名  ip列表等信息
  printf("host name is [%s] \n", host_entry->h_name);
  printf("the other name is :\n");
  for(int i=0; host_entry->h_aliases[i]; ++i)
  {
    printf("   [i:%d][%s] \n", i, host_entry->h_aliases[i]);
  }
  printf("Address type: %s\n", (host_entry->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6");
  printf("address length: %d \n", host_entry->h_length);
  printf("ip list is :\n");
  for(int i=0; host_entry->h_addr_list[i]; ++i)
  {
    // printf("   [i:%d][%s] \n", i, host_entry->h_addr_list[i]);
    printf("   [i:%d][%s] \n", i, inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[i]));
  }
  return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
}
//创建socket 根据服务器ip和断开 进行连接 返回fd
int create_http_socket(const char * ip)
{
  //创建socket 根据Ip:port连接对端
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if(sockfd < 0)
  {
    return -1;
  }
  struct sockaddr_in sin = {0};
    sin.sin_addr.s_addr = inet_addr(ip);
    sin.sin_port = htons(80);
    sin.sin_family = AF_INET;
    //连接服务器 
    if (-1 == connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
      printf("connect http server error \n");
        return -1;
    }
    //设置fd非阻塞
  fcntl(sockfd, F_SETFL, O_NONBLOCK);    
  return sockfd;
}
#define HTTP_VERSION    "HTTP/1.1"
//好像不同的浏览器有不同的一些参数  可以在网页用f12请求进行查看 
#define USER_AGENT      "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\n"
#define ENCODE_TYPE     "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
#define CONNECTION_TYPE "Connection: close\r\n"
#define BUFFER_SIZE 4096
//发送http请求 返回发送成功还是失败
int send_http_request(int fd, const char * hostname, const char* resource)
{
  char send_buff[BUFFER_SIZE] = {0};
  //构造发送的请求报文  请求行 请求头部 空行  请求数据
  // GET /teacher_6.jpg HTTP/1.1
  // Host: www.0voice.com
  // User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36
  // Content-Type: application/x-www-form-urlencoded
  // Content-Length: 9
  // lingsheng
//只关注了必须的参数  测试一下
  // GET %s %s\r\n   resource  HTTP_VERSION   请求行
  // Host: %s\r\n    hostname         请求头
  // %s\r\n          CONNECTION_TYPE      请求头
  // \r\n
  int len = sprintf(send_buff, "GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",\
    resource, HTTP_VERSION, hostname, CONNECTION_TYPE);
  printf("send request buff is [%lu][%s] \n", strlen(send_buff), send_buff);
  int buff_len = strlen(send_buff);
  int sendlen = send(fd, send_buff, strlen(send_buff), 0);
  if(buff_len != sendlen)
  {
    printf("send buffer error \n");
    return -1;
  }
  return 0;
}
//获取回应消息并打印 监听对端的回应进行打印 返回成功或者失败
int get_recv_http_response(int sockfd)
{
  //使用io多路复用select对接收 进行输出
  struct timeval tv;
    tv.tv_sec = 5;
    tv.tv_usec = 0;
  fd_set fdread;
    FD_ZERO(&fdread);
    FD_SET(sockfd, &fdread);
    //存储最终结果
    char *result = malloc(sizeof(int));
    result[0] = '\0';
    //存储每次recv的数据
    int len = 0;
    char buffer[BUFFER_SIZE] = {0};
    while (1) {
        int selection = select(sockfd+1, &fdread, NULL, NULL, &tv);
        if (!selection || !(FD_ISSET(sockfd, &fdread))) {
            break;
        } else {
          //这里是客户端  只针对这个连接的场景
            len = recv(sockfd, buffer, BUFFER_SIZE, 0);
            if (len == 0) break;
      //重新申请大小并对接收到的数据进行追加 
            result = realloc(result, (strlen(result) + len + 1) * sizeof(char));
            strncat(result, buffer, len);
        }
    }
    if(result == NULL)
    {
      return -1;
    }
    //打印并释放接收到的数据
    puts(result);
    free(result);
  return 0;
}

3:测试结果:

如,请求baidu的主页,有的url是https的,这里暂时只是http的练习,https的相关需要安全认证。

./XXX www.baidu.com /

./XXX api.seniverse.com /v3/weather/now.json?key=0pyd8z7jouficcil&location=beijing&language=zh-Hans&unit=c

hlp@ubuntu:~/220107$ ./get www.baidu.com /
... #这里打印过多 省略
hlp@ubuntu:~/220107$ ./get api.seniverse.com /v3/weather/now.json?key=0pyd8z7jouficcil&location=hefei&language=zh-Hans&unit=c
[1] 9103
[2] 9104
[3] 9105
hlp@ubuntu:~/220107$ host name is [api.seniverse.com] 
the other name is :
Address type: AF_INET
address length: 4 
ip list is :
   [i:0][116.62.81.138] 
get ip is [116.62.81.138] 
send request buff is [104][GET /v3/weather/now.json?key=0pyd8z7jouficcil HTTP/1.1
Host: api.seniverse.com
Connection: close
] 
HTTP/1.1 400 Bad Request
Date: Sun, 13 Feb 2022 14:26:26 GMT
Content-Type: application/json
Content-Length: 55
Connection: close
access-control-allow-origin: *
x-request-id: FtNerTOXZFLlW0IvMUaB
{"status":"Wrong parameters.","status_code":"AP010001"}

我开始试着积累一些常用代码:自己代码库中备用

我的知识储备更多来自这里,推荐你了解:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习

相关实践学习
基于函数计算快速搭建Hexo博客系统
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
目录
相关文章
|
2天前
|
JSON 编解码 Go
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第25天】Go语言`net/http`包提供HTTP客户端和服务器功能,简化高性能网络应用开发。本文探讨如何发起HTTP请求,常见问题及解决策略。示例展示GET和POST请求的实现。注意响应体关闭、错误处理、内容类型设置、超时管理和并发控制。最佳实践包括重用`http.Client`,使用`context.Context`,处理JSON以及记录错误日志。通过实践这些技巧,提升HTTP编程技能。
16 1
|
2天前
|
前端开发 API UED
AngularJS的$http服务:深入解析与进行HTTP请求的技术实践
【4月更文挑战第28天】AngularJS的$http服务是核心组件,用于发起HTTP请求与服务器通信。$http服务简化了通信过程,通过深入理解和实践,能构建高效、可靠的前端应用。
|
4天前
|
Go 开发者
Golang深入浅出之-HTTP客户端编程:使用net/http包发起请求
【4月更文挑战第24天】Go语言的`net/http`包在HTTP客户端编程中扮演重要角色,但使用时需注意几个常见问题:1) 检查HTTP状态码以确保请求成功;2) 记得关闭响应体以防止资源泄漏;3) 设置超时限制,避免长时间等待;4) 根据需求处理重定向。理解这些细节能提升HTTP客户端编程的效率和质量。
15 1
|
5天前
|
存储 缓存 开发框架
Flutter的网络请求:使用Dart进行HTTP请求的技术详解
【4月更文挑战第26天】了解Flutter网络请求,本文详述使用Dart进行HTTP请求
|
6天前
|
JSON 数据格式 索引
ES 查看索引的属性的http请求
在 Elasticsearch 中,要查看索引的属性,可以通过发送 HTTP 请求来执行以下操作: 1. **获取索引的映射(Mapping)**: 可以使用 `GET` 请求访问 Elasticsearch 的 `_mapping` 端点来获取特定索引的映射信息。 示例请求: ```http GET http://<elasticsearch_host>:<port>/<index_name>/_mapping ``` 2. **获取索引的设置(Settings)**: 可以使用 `GET` 请求访问 Elasticsearch 的 `_setting
|
2月前
|
前端开发
webpack如何设置devServer启动项目为https协议
webpack如何设置devServer启动项目为https协议
148 0
|
4月前
|
Web App开发 移动开发 JavaScript
Python网络编程(三),HTTP协议
Python网络编程(三),HTTP协议
62 0
|
1天前
|
安全 网络协议 算法
【计算机网络】http协议的原理与应用,https是如何保证安全传输的
【计算机网络】http协议的原理与应用,https是如何保证安全传输的
|
2天前
|
网络协议 安全 算法
HTTP协议与HTTPS协议
HTTP协议与HTTPS协议