•  无论是Android还是ios都离不开与服务器交互,这就必须用到网络请求,记得在2013年做iOS的时候那时候用的ASIHTTPRequest框架,现在重新捡起iOS的时候ASIHTTPRequest已经停止维护,大家都在用AFNetWorking作为首选...

    前言:

        无论是Android还是ios都离不开与服务器交互,这就必须用到网络请求,记得在2013年做iOS的时候那时候用的ASIHTTPRequest框架,现在重新捡起iOS的时候ASIHTTPRequest已经停止维护,大家都在用AFNetWorking作为首选网络请求框架,之前的ASIHTTPRequest是基于NSURLConnection类实现的,早期的AFNetWorking也是基于NSURLConnection实现,后来iOS9 之后已经放弃了NSURLConnection,开始使用iOS 7之后推出的NSURLSession,本着追根溯源的原则,首先学习一下NSURLSession的实现网络请求,然后再去学习AFNetWorking。

     了解NSURLSession

        NSURLSession是2013年iOS 7发布的用于替代NSURLConnection的,iOS 9之后NSURLConnection彻底推出历史舞台。其使用起来非常方便,今天使用NSURLConnection分别实现了get、post、表单提交、文件上传、文件下载,让我这个以Android开发为主的屌丝程序员赞叹不已,根据NSURLSession会话对象创建一个请求Task,然后执行该Task即可,包括缓存、会话周期,多线程任务iOS都已经在sdk层面封装完毕,不过比较遗憾的时NSURLSession只提供了异步请求方式而没有提供同步请求方式。接下来我们来如何实现网络请求。

    NSURLSession使用

      我们首先以一个简单的get请求为例开始。

    1.)首先构造一个NSURL请求资源地址

       // 构造URL资源地址
       NSURL *url = [NSURL URLWithString:@"http://api.nohttp.net/method?name=yanzhenjie&pwd=123"];

    2.)创建一个NSRequest请求对象

        // 创建Request请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        // 配置Request请求
        // 设置请求方法
        [request setHTTPMethod:@"GET"];
        // 设置请求超时 默认超时时间60s
        [request setTimeoutInterval:10.0];
        // 设置头部参数
        [request addValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
        //或者下面这种方式 添加所有请求头信息
        request.allHTTPHeaderFields=@{@"Content-Encoding":@"gzip"};
        //设置缓存策略
        [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];

    根据需求添加不用的设置,比如请求方式、超时时间、请求头信息,这里重点介绍下缓存策略:

    • NSURLRequestUseProtocolCachePolicy = 0 //默认的缓存策略, 如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端.
    • NSURLRequestReloadIgnoringLocalCacheData = 1 //忽略本地缓存数据,直接请求服务端.
    • NSURLRequestIgnoringLocalAndRemoteCacheData = 4 //忽略本地缓存,代理服务器以及其他中介,直接请求源服务端.
    • NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData
    • NSURLRequestReturnCacheDataElseLoad = 2 //有缓存就使用,不管其有效性(即忽略Cache-Control字段), 无则请求服务端.
    •  NSURLRequestReturnCacheDataDontLoad = 3 //只加载本地缓存. 没有就失败. (确定当前无网络时使用)
    • NSURLRequestReloadRevalidatingCacheData = 5 //缓存数据必须得得到服务端确认有效才使用

    3.)创建NSURLSession会话对象

    可以通过采用iOS共享Session的方式

        // 采用苹果提供的共享session
        NSURLSession *sharedSession = [NSURLSession sharedSession];

    可以通过NSURLSessionConfiguration方式配置不同的NSURLSession

        // 构造NSURLSessionConfiguration
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        // 构造NSURLSession,网络会话;
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];

    通过NSURLSessionConfiguration提供了三种创建NSURLSession的方式

    • defaultSessionConfiguration //默认配置使用的是持久化的硬盘缓存,存储证书到用户钥匙链。存储cookie到shareCookie。
    • ephemeralSessionConfiguration //不使用永久持存cookie、证书、缓存的配置,最佳优化数据传输。
    • backgroundSessionConfigurationWithIdentifier //可以上传下载HTTP和HTTPS的后台任务(程序在后台运行)。
      在后台时,将网络传输交给系统的单独的一个进程,即使app挂起、推出甚至崩溃照样在后台执行。

    也可以通过NSURLSessionConfiguration统一设置超时时间、请求头等信息

        // 构造NSURLSessionConfiguration
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        //设置请求超时为10秒钟
        configuration.timeoutIntervalForRequest = 10;
        
        //在蜂窝网络情况下是否继续请求(上传或下载)
        configuration.allowsCellularAccess = NO;
    
        //配置请求头
        configuration.HTTPAdditionalHeaders =@{@"Content-Encoding":@"gzip"};

    4.) 创建NSURLSessionTask对象,然后执行

        // 构造NSURLSessionTask,会话任务;
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            // 请求失败,打印错误信息
            if (error) {
                NSLog(@"get error :%@",error.localizedDescription);
            }
            //请求成功,解析数据
            else {
                // JSON数据格式解析
                id object = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
                // 判断是否解析成功
                if (error) {
                    NSLog(@"get error :%@",error.localizedDescription);
                }else {
                    NSLog(@"get success :%@",object);
                    // 解析成功,处理数据,通过GCD获取主队列,在主线程中刷新界面。
                    dispatch_async(dispatch_get_main_queue(), ^{
                        // 刷新界面....
                    });
                }
            }
        }];

    iOS为了适应不同的应用场景提供了不同类型的NSSessionTask

    • NSURLSessionDataTask  //一般的get、post等请求
    • NSURLSessionUploadTask // 用于上传文件或者数据量比较大的请求
    • NSURLSessionDownloadTask //用于下载文件或者数据量比较大的请求
    • NSURLSessionStreamTask //建立一个TCP / IP连接的主机名和端口或一个网络服务对象。

    task的三个函数

    • - (void)suspend;//暂停
    • - (void)resume;//开始或者恢复
    • - (void)cancel;//关闭任务

    NSURLSession其他请求示例

     1.)post请求

        // 1、创建URL资源地址
        NSURL *url = [NSURL URLWithString:@"http://api.nohttp.net/postBody"];
        // 2、创建Reuest请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        // 3、配置Request
        // 设置请求超时
        [request setTimeoutInterval:10.0];
        // 设置请求方法
        [request setHTTPMethod:@"POST"];
        // 设置头部参数
        [request addValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
        // 4、构造请求参数
        // 4.1、创建字典参数,将参数放入字典中,可防止程序员在主观意识上犯错误,即参数写错。
        NSDictionary *parametersDict = @{@"name":@"yanzhenjie",@"pwd":@"123"};
        // 4.2、遍历字典,以“key=value&”的方式创建参数字符串。
        NSMutableString *parameterString = [[NSMutableString alloc]init];
        int pos =0;
        for (NSString *key in parametersDict.allKeys) {
            // 拼接字符串
            [parameterString appendFormat:@"%@=%@", key, parametersDict[key]];
            if(pos<parametersDict.allKeys.count-1){
                [parameterString appendString:@"&"];
            }
            pos++;
        }
        // 4.3、NSString转成NSData数据类型。
        NSData *parametersData = [parameterString dataUsingEncoding:NSUTF8StringEncoding];
        // 5、设置请求报文
        [request setHTTPBody:parametersData];
        // 6、构造NSURLSessionConfiguration
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        // 7、创建网络会话
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
        // 8、创建会话任务
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            // 10、判断是否请求成功
            if (error) {
                NSLog(@"post error :%@",error.localizedDescription);
            }else {
                // 如果请求成功,则解析数据。
                id object = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error];
                // 11、判断是否解析成功
                if (error) {
                    NSLog(@"post error :%@",error.localizedDescription);
                }else {
                    // 解析成功,处理数据,通过GCD获取主队列,在主线程中刷新界面。
                    NSLog(@"post success :%@",object);
                    dispatch_async(dispatch_get_main_queue(), ^{
                        // 刷新界面...
                    });
                }
            }
            
        }];
        // 9、执行任务
        [task resume];

    2.)附带表单参数文件上传

        NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];
        NSTimeInterval a=[dat timeIntervalSince1970];
        NSString* fileName = [NSString stringWithFormat:@"file_%0.f.txt", a];
        
        [FileUtils writeDataToFile:fileName data:[@"upload_file_to_server" dataUsingEncoding:NSUTF8StringEncoding]];
        
        // 以流的方式上传,大小理论上不受限制,但应注意时间
        // 1、创建URL资源地址
        NSURL *url = [NSURL URLWithString:@"http://api.nohttp.net/upload"];
        // 2、创建Reuest请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        // 3、配置Request
        //设置Body值方法二,这种方法比较原始,不常用,不过可以用来上传参数和文件
        NSString *BOUNDARY = @"whoislcj";//表单分界线 可以自定义任意值
        [request setValue:[@"multipart/form-data; boundary=" stringByAppendingString:BOUNDARY] forHTTPHeaderField:@"Content-Type"];
        // 文件上传使用post
        [request setHTTPMethod:@"POST"];
        // 设置请求超时
        [request setTimeoutInterval:30.0f];
        //用于存放二进制数据流
        NSMutableData *body = [NSMutableData data];
        
        //追加一个普通表单参数 name=yanzhenjie
        NSString *nameParam = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n",BOUNDARY,@"name",@"yanzhenjie",nil];
        [body appendData:[nameParam dataUsingEncoding:NSUTF8StringEncoding]];
        
        //追加一个普通表单参数 pwd=123
        NSString *pwdParam = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\"\r\n\r\n%@\r\n",BOUNDARY,@"pwd",@"123",nil];
        [body appendData:[pwdParam dataUsingEncoding:NSUTF8StringEncoding]];
        
        //追加一个文件表单参数
        // Content-Disposition: form-data; name="<服务器端需要知道的名字>"; filename="<服务器端这个传上来的文件名>"
        // Content-Type: application/octet-stream --根据不同的文件类型选择不同的值
        NSString *file = [NSString stringWithFormat:@"--%@\r\nContent-Disposition: form-data; name=\"%@\";filename=\"%@\"\r\nContent-Type: application/octet-stream\r\n\r\n",BOUNDARY,@"headUrl",fileName,nil];
        [body appendData:[file dataUsingEncoding:NSUTF8StringEncoding]];
        //获取file路径
        NSString *filePath =[FileUtils getFilePath:fileName];
        NSData *data =[NSData dataWithContentsOfFile:filePath];
        //追加文件二进制数据
        [body appendData:data];
        [body appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
        //结束分割线
        NSString *endString = [NSString stringWithFormat:@"--%@--",BOUNDARY];
        [body appendData:[endString dataUsingEncoding:NSUTF8StringEncoding]];
        
        // 创建会话
        NSURLSession *session = [NSURLSession sharedSession];
        // 3.开始上传   request的body data将被忽略,而由fromData提供
        NSURLSessionUploadTask *uploadTask= [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                NSLog(@"upload error:%@",error);
            } else {
                NSLog(@"upload success:%@", [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&error]);
                dispatch_async(dispatch_get_main_queue(), ^{
                    // 刷新界面...
                });
            }
        }];
        //执行任务
        [uploadTask resume];

    3.)文件下载

        // 创建url
        NSString *urlStr =@"http://images2015.cnblogs.com/blog/950883/201701/950883-20170105104233581-62069155.png";
        NSURL *Url = [NSURL URLWithString:urlStr];
        // 创建请求
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:Url];
        // 设置请求超时
        [request setTimeoutInterval:30.0];
        // 创建会话
        NSURLSession *session = [NSURLSession sharedSession];
        
        NSURLSessionDownloadTask *downLoadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (!error) {
                NSLog(@"download sucess : %@", location);
                NSData *data=[NSData dataWithContentsOfURL:location];
                UIImage *image=[UIImage imageWithData:data];
                dispatch_async(dispatch_get_main_queue(), ^{
                    // 刷新界面...
                    UIImageView *imageView =[[UIImageView alloc]init];
                    imageView.image=image;
                    [self.view addSubview:imageView];
                    [imageView mas_makeConstraints:^(MASConstraintMaker *make) {
                        make.center.equalTo(self.view);
                        make.size.mas_equalTo(CGSizeMake(300, 300));
                    }];
                });
            } else {
                NSLog(@"download error : %@", error.localizedDescription);
            }
        }];
        //启动任务
        [downLoadTask resume];

    总结:

      今天学习了iOS底层如何实现网络请求的,为了开发效率还得依靠优秀的第三方开源框架,接下来抽时间学习一下受广大iOS开发者亲赖的网络开源框架AFNetWorking。

    展开全文
  • 集成百度地图SDK(swift)

    2016-05-12 02:07:37
    一 集成百度地图最终的效果二 知识点1 信息检索2 地图大头针和导航SDK三 集成百度地图SDK1 百度搜索百度地图SDK2 注册账号(尽量)–>如果没有账号,最好去注册一个账号(我这里就不细说注册账号的步骤了)3 由于我们要做...

    一 集成百度地图最终的效果

    这里写图片描述

    二 知识点

    1 信息检索

    2 地图大头针和导航SDK

    三 集成百度地图SDK

    1 百度搜索百度地图SDK

    这里写图片描述

    2 注册账号(尽量)–>如果没有账号,最好去注册一个账号(我这里就不细说注册账号的步骤了)

    3 由于我们要做的是和百度地图相关的,所以直接按照下面图片点击即可(这里我将一一介绍ios中地图的相关开发)

    这里写图片描述

    4 点击ios地图SDK里面是一些相关概述

    5 按照图片点击进入下面

    这里写图片描述

    6 相关解释

    6.1 一般我们都是直接点击”全部下载”
    6.2 解释 : “Bitcode版”是什么意思:在上架的时候,如果勾选了bitcode版,那么在上架打包的时候,把包发送到contect里面(管理所有的应用程序),就可以上传到app store了,用户就可以来下载了.但是有一个弊端,如果手机应用既适配了4也适配了6p,4使用的图片是使用2x,6p使用的是3x的图片.如果是以前的情况,是将所有的都打包发送,里面的所有资源都包括.但是如果你的手机是4s,那么就会造成空间浪费.Bitcode的作用就是进行解包,就是根据手机相应的类型来给用户对于的图片,这样大大的减小了应用的大小.但是并不是所有的SDK都支持Bitcode,如果在集成第三方框架的时候,有可能会报Bitcode的错误.这样怎么办呢?(把下面改成NO就可以了).

    这里写图片描述

    7 自定义下载(如果你只想做其中一种功能的话,就才用自定义下载)

    这里写图片描述

    8 点击全部下载

    9 获取秘钥

    这里写图片描述

    10 创建应用(点击提交)

    这里写图片描述

    11 将获取到的key保存起来(1Yl6AGhTBKPupy4fM6pTHGO6sFIoTtzi)

    这里写图片描述

    12 点击开发,回到概述,查看开发指南相关信息,点击注意事项

    这里写图片描述

    13 什么时候会使用到-ObjC?当SDK或者静态库使用到分类,那么需要使用-ObjC来让他们之间产生链接(注意书写).按照上面的路径填写在Xcode的Project -> Edit Active Target -> Build Setting -> Other Linker Flags中添加-ObjC

    14 在info.plist中配置信息

    这里写图片描述

    15 将下载好的百度SDK添加到工程当中

    这里写图片描述
    这里写图片描述

    16 按照步骤,导入系统的库文件

    这里写图片描述
    这里写图片描述

    17 导入完成后的结果

    这里写图片描述

    18 引入mapapi.bundle资源文件

    这里写图片描述

    19 mapapi.bundle位置: (在下载的百度SDK库中)

    这里写图片描述

    20 在15步骤中是否已经导入了mapapi.bundl需要用的资源包?没有,因为,如果我们是将15步骤中下载的SDK需要用到的东西一次性导入的话,就不会导入mapapi.bundl,需要我们一个一个添加才可以.

    这里写图片描述

    21 由于我们是采用swift的,所以,如果用到OC需要桥接;导入头文件(记住:头文件里面有一处错误)

    这里写图片描述

    二 该部分相关的代码

    1 百度代码OC视图

    这里写图片描述

    //懒加载
        private lazy var mapManager :BMKMapManager = {
           return BMKMapManager()
        }()
    
        func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
            //如果需要关注网络及授权验证事件,请设定generalDelegate参数
            let ret = mapManager.start("1Yl6AGhTBKPupy4fM6pTHGO6sFIoTtzi", generalDelegate: nil)
            if ret == false {
                print("manager start failed!")
            }
            return true
        }

    2 该部分写好代码后,需要考虑的是要用什么来展示地图?

    —-> 1.1 解答 : 通过查看BMKMapView我们知道它是继承UIView的,那么我们可以往storyboard中拖一个UIView,然后让其类型等于BMKMapView就可以展示了.

    3 运行时候弹出的错误 : (这个在百度API中注意事项也有说明,只要配置下info.plist就可以了)

    这里写图片描述

    三 信息检索

    1 检索行为 : 检索周边区域有什么小吃,电影院,宾馆等

    2 信息检索位置

    这里写图片描述

    3 检索代码

    —-> 3.1 懒加载和代理相关定义
    class ViewController: UIViewController {
        //展示地图的view
        @IBOutlet weak var mapView: BMKMapView!
        //懒加载
        private lazy var searcher : BMKPoiSearch = {
            //初始化检索对象
            let searcher = BMKPoiSearch()
            //设置代理
            searcher.delegate = self
            return searcher
        }()
    
        override func viewWillAppear(animated: Bool) {
            super.viewWillAppear(animated)
            mapView.delegate = self
        }
    
        override func viewWillDisappear(animated: Bool) {
            super.viewWillAppear(animated)
            //将代理设置为nil,以免占用内存
            mapView.delegate = nil
        }
    }
    —-> 3.2 代理方法实现和判断是否检索成功
    ///MARK: - 实现代理方法(当用户长按的时候回调用)
    extension ViewController : BMKMapViewDelegate {
        func mapview(mapView: BMKMapView!, onLongClick coordinate: CLLocationCoordinate2D) {
            //发起检索
            let option = BMKNearbySearchOption()
            //检索的第几页
            option.pageIndex = 0
            //每页检索的数量
            option.pageCapacity = 20
            //检索的区域
            option.location = CLLocationCoordinate2DMake(39.915, 116.404)
            //检索的关键字
            option.keyword = "电影"
            //开始检索
            let flag = searcher.poiSearchNearBy(option)
            if flag {
                print("周边检索发送成功")
            }else {
                print("周边检索发送失败")
            }
        }
    }
    —-> 3.3 结果回调
    extension ViewController : BMKPoiSearchDelegate {
        func onGetPoiResult(searcher: BMKPoiSearch!, result poiResult: BMKPoiResult!, errorCode: BMKSearchErrorCode) {
            if errorCode == BMK_SEARCH_NO_ERROR {
                //处理正常结果
                print("获取到数据")
            }else if errorCode == BMK_SEARCH_AMBIGUOUS_KEYWORD {
                print("起始点有歧义")
            }else {
                print("抱歉,未找到结果")
            }
        }
    }

    4 注意一 : 如果按照百度上面提供的代码书写位置,是有问题的.因为当view还没加载完毕的时候,就去请求检索了,这是不会成功的.(修改书写位置)

    5 注意二 : 需要在view将要消失的时候,将代理赋值为空,否则会影响内存

    四 导航SDK

    1 点击下面图片,进入导航SDK界面

    这里写图片描述

    2 获取秘钥(该部分在前面已经获取了,这里不需要再重新获取)

    3 在下图的地方下载导航的SDK

    这里写图片描述

    4 将下载好的第三方导航SDK文件导入app中

    这里写图片描述

    5 导入相关框架

    这里写图片描述

    6 SDK导入工程中,编译后仍旧会报错,这是我们只需要设置下面图片就可以了,现在基本都是ARC模式.如果是非ARC模式下,就需要按照这样配置在Build Settings中, “Other Linker Flags”添加“-ObjC” 标识

    —-> 6.1 ARC模式配置

    这里写图片描述

    —-> 6.2 非ARC模式配置

    这里写图片描述

    7 在info.plist文件中进行相关配置(前后台设置)

    8 将#import “BNCoreServices.h”导入桥接文件中

    五 地图大头针和导航代码(该部分不好说,我直接给大家写上总体代码了)

    //点弹框时调用该方法
        func mapView(mapView: BMKMapView!, annotationViewForBubble view: BMKAnnotationView!) {
            //获取当前大头针的坐标
            let coodinate = view.annotation.coordinate
    
            //节点数组
            var nodesArray = [BNRoutePlanNode]()
            //起点
            let startNode = BNRoutePlanNode()
            startNode.pos = BNPosition()
            startNode.pos.x = coodinate.longitude
            startNode.pos.y = coodinate.latitude + 0.1
            startNode.pos.eType = BNCoordinate_BaiduMapSDK
            nodesArray.append(startNode)
    
            //终点
            let endNode = BNRoutePlanNode()
            endNode.pos = BNPosition()
            endNode.pos.x = coodinate.longitude
            endNode.pos.y = coodinate.latitude
            endNode.pos.eType = BNCoordinate_BaiduMapSDK
            nodesArray.append(endNode)
    
            //发起路径规划
            BNCoreServices.RoutePlanService().startNaviRoutePlan(BNRoutePlanMode_Recommend, naviNodes: nodesArray, time: nil, delegete: self, userInfo: nil)
        }
    
        //区域发生改变会调用该方法
        func mapView(mapView: BMKMapView!, regionDidChangeAnimated animated: Bool) {
            print(mapView.region.span)
        }
    }
    
    extension ViewController : BNNaviRoutePlanDelegate {
        func routePlanDidFinished(userInfo: [NSObject : AnyObject]!) {
            BNCoreServices.UIService().showNaviUI(BN_NaviTypeSimulator, delegete: nil, isNeedLandscape: true)
        }
    }
    
    extension ViewController : BMKPoiSearchDelegate {
        func onGetPoiResult(searcher: BMKPoiSearch!, result poiResult: BMKPoiResult!, errorCode: BMKSearchErrorCode) {
            if errorCode == BMK_SEARCH_NO_ERROR {
                //处理正常结果
                let poiInfos = poiResult.poiInfoList as! [BMKPoiInfo]
                //遍历
                for poiInfo in poiInfos {
                    //添加一个PointAnnotation
                    let annotation = BMKPointAnnotation()
                    annotation.coordinate = poiInfo.pt
                    annotation.title = poiInfo.name
                    annotation.subtitle = poiInfo.address
                    //添加大头针
                    mapView.addAnnotation(annotation)
                }
            }else if errorCode == BMK_SEARCH_AMBIGUOUS_KEYWORD {
                print("起始点有歧义")
            }else {
                print("抱歉,未找到结果")
            }
        }
    }

    1 将代码写完后运行,报错,下面显示的是错误信息.

    这里写图片描述

    —-> 1.1 解决 : 没有开启后台模式,直接开启后台模式

    这里写图片描述

    2 遗留的两个问题

    —-> 2.1 弹框提示TTS失败?
    —-> 2.2 导航的过程中没有声音 : 到开发文档中点击语音播报,创建应用填入bundleID 以及key

    3 解决问题一 :(语音播报)

    —-> 3.1 :

    这里写图片描述

    —-> 3.2 :

    这里写图片描述

    —-> 3.3 :

    这里写图片描述

    4 解决问题二 : (弹框失败)

    这里写图片描述

    六 总结

    1 该部分可以说是一个完整的集成第三方库实现的地图导航,里面有些问题解决的并不是很好,希望看到我博客的同门能给我提出一些建议,或者我们可以一起讨论.

    2 最后,如果大家觉得我写的博客还行,麻烦大家多多关注我的官方博客,谢谢!!!!

    展开全文
  • 一个linux下简单的纯C++实现Http请求类(GET,POST,上传,下载) 文章目录一个linux下简单的纯C++实现Http请求类(GET,POST,上传,下载)@[toc]HttpRequest类设计 最近写了点关于Http上传下载文件相关的,于是...

    一个linux下简单的纯C++实现Http请求类(GET,POST,上传,下载)


    最近写了点关于Http上传下载文件相关的,于是今天整理下代码。

    Http协议简述
    HttpRequest类设计
    使用示例

    Http协议简述

    协议:网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流,就好比两台计算机交互的语言.

    HTTP协议:超文本传输协议(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。主要被用于在Web浏览器和网站服务器之间传递信息。 HTTP 是基于 TCP/IP 协议的应用层协议。默认使用80端口。最新版本是HTTP 2.0,目前是用最广泛的是HTTP 1.1。

    HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

    请求方法:
    HTTP/1.1协议中共定义了八种方法(有时也叫“动作”)来表明Request-URI指定的资源的不同操作方式:
    OPTIONS - 返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送’*'的请求来测试服务器的功能性。
    HEAD- 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新。
    GET - 向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在web app.中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
    POST - 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
    PUT - 向指定资源位置上传其最新内容。
    DELETE - 请求服务器删除Request-URI所标识的资源。
    TRACE- 回显服务器收到的请求,主要用于测试或诊断。
    CONNECT - HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
    PATCH - 用来将局部修改应用于某一资源,添加于规范RFC5789。
    方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed);当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
    HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。

    GET和POST请求的区别
    GET请求
    GET /books/?sex=man&name=Professional HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Connection: Keep-Alive
    注意最后有一行空行

    POST请求
    POST / HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 40
    Connection: Keep-Alive

    name=Professional%20Ajax&publisher=Wiley

    1、GET提交:请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,多个参数用&连接;例 如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0 %E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。

    2、POST提交:把提交的数据放置在是HTTP包的包体中。上文示例中红色字体标明的就是实际的传输数据

    HttpRequest类设计

    请求部分

    HttpRequest::HttpRequest() 通过传入Url构造HttpUrl类分离url域名及uri.
    HttpRequest::connect() 通过gethostbyname()获取域名ip,与80端口组成远端地址建立链接.
    HttpRequest::setRequestMethod() 设置请求方法,目前只添加了Get和Post请求.
    HttpRequest::setRequestProperty() 设置属性.
    HttpRequest::setRequestBody() 设置content.
    HttpRequest::send() 将设置的请求流发送出去.

    应答部分

    HttpRequest::handRead() 处理服务器应答头.
    HttpRequest::getResponseCode() handRead()后可以获取到应答code
    HttpRequest::getResponseProperty() handRead()后可以获取到对应的应答属性
    HttpRequest::getResponseContent() handRead()后可以获取到应答content

    #ifndef _HTTP_REQUEST_HH
    #define _HTTP_REQUEST_HH
    
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <algorithm>
    #include <vector>
    #include <string>
    #include <assert.h>
    #include <string.h>
    #include <sstream>
    #include <map>
    #include "Logger.hh"
    
    const size_t kBufferSize = 4096;
    
    /// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer
    ///
    /// @code
    /// +-------------------+------------------+------------------+
    /// | prependable bytes |  readable bytes  |  writable bytes  |
    /// |                   |     (CONTENT)    |                  |
    /// +-------------------+------------------+------------------+
    /// |                   |                  |                  |
    /// 0      <=      readerIndex   <=   writerIndex    <=     size
    /// @endcode
    class Buffer
    {
    public:
    
      static const size_t kCheapPrepend = 8;
      static const size_t kInitialSize = 4096;
    //public:
    
    	explicit Buffer(size_t initialSize = kInitialSize)
    	: m_buffer(kCheapPrepend + initialSize),
    	  m_readerIndex(kCheapPrepend),
    	  m_writerIndex(kCheapPrepend)
    	  {
    	    assert(readableBytes() == 0);
            assert(writableBytes() == initialSize);
            assert(prependableBytes() == kCheapPrepend);
    	  }
    
      size_t readableBytes() const
      { return m_writerIndex - m_readerIndex; }
    
      size_t writableBytes() const
      { return m_buffer.size() - m_writerIndex; }
    
      size_t prependableBytes() const
      { return m_readerIndex; }
    
      const char* peek() const
      { return begin() + m_readerIndex; }
    
      char* beginWrite()
      { return begin() + m_writerIndex; }
    
      void hasWritten(size_t len)
      {
        assert(len <= writableBytes());
        m_writerIndex += len;
      }
    
      void unwrite(size_t len)
      {
        assert(len <= readableBytes());
        m_writerIndex -= len;
      }
    
      // retrieve returns void, to prevent
      // string str(retrieve(readableBytes()), readableBytes());
      // the evaluation of two functions are unspecified
      void retrieve(size_t len)
      {
        assert(len <= readableBytes());
        if (len < readableBytes())
        {
          m_readerIndex += len;
        }
        else
        {
          retrieveAll();
        }
      }
    
      void retrieveAll()
      {
      	m_readerIndex = kCheapPrepend;
      	m_writerIndex = kCheapPrepend;
      }
    
    private:
      char* begin()
      {return &*m_buffer.begin(); }
    
      const char* begin() const
      {return &*m_buffer.begin(); }
    
    private:
    	std::vector<char> m_buffer;
    	size_t m_readerIndex;
    	size_t m_writerIndex;
    };
    
    
    namespace sockets
    {
    
    /// Creates a non-blocking socket file descriptor,
    /// abort if any error.
    int createSocket(sa_family_t family);
    
    int  connect(int sockfd, const struct sockaddr* addr);
    ssize_t read(int sockfd, void *buf, size_t count);
    ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);
    ssize_t write(int sockfd, const void *buf, size_t count);
    void close(int sockfd);
    
    void fromIpPort(const char* ip, uint16_t port,
                    struct sockaddr_in* addr);
    
    int getSocketError(int sockfd);
    void delaySecond(int sec);
    //const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr)
    //const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr);
    
    }
    
    class InetAddress
    {
     public:
      /// Constructs an endpoint with given ip and port.
      /// @c ip should be "1.2.3.4"
      InetAddress(std::string ip, uint16_t port);
    
      /// Constructs an endpoint with given struct @c sockaddr_in
      /// Mostly used when accepting new connections
      explicit InetAddress(const struct sockaddr_in& addr)
        : m_addr(addr)
      { }
    
      sa_family_t family() const { return m_addr.sin_family; }
      //std::string toIp() const;
      //std::string toIpPort() const;
    
      const struct sockaddr* getSockAddr() const { return (struct sockaddr*)(&m_addr); }
    
      uint32_t ipNetEndian() const;
    
      // resolve hostname to IP address, not changing port or sin_family
      // return true on success.
      // thread safe
      // static bool resolve(StringArg hostname, StringArg* ip);
      // static std::vector<InetAddress> resolveAll(const char* hostname, uint16_t port = 0);
    
     private:
        struct sockaddr_in m_addr;
    
    };
    
    
    class HttpUrl
    {
    public:
    	HttpUrl(std::string& httpUrl)
    	:m_httpUrl(httpUrl),
    	 m_smatch(detachHttpUrl())
    	{
    		LOG_DEBUG << "URL : " << m_httpUrl;
    	}
    	~HttpUrl(){};
    
    	enum HttpUrlMatch
    	{
    		URL = 0,
    		HOST = 1,
    		URI = 2
    	};
    
    	std::vector<std::string> detachHttpUrl() const
    	{
    	  std::vector<std::string> v;
    	  std::string::size_type pos1, pos2;
    	  pos2 = m_httpUrl.find('/');
    	  assert(std::string::npos != pos2);
    	  pos1 = pos2 + 2;
    	  pos2 = m_httpUrl.find('/', pos1);
    	  assert(std::string::npos != pos2);
    	  v.push_back(m_httpUrl);
    	  v.push_back(m_httpUrl.substr(pos1, pos2 - pos1));
    	  v.push_back(m_httpUrl.substr(pos2 + 1));
    	  LOG_DEBUG << "detachHttpUrl() url :" << v[0];
    	  LOG_DEBUG << "detachHttpUrl() host :" << v[1];
    	  LOG_DEBUG << "detachHttpUrl() uri :" << v[2];
    	  return v;
    	}
    
    	bool HttpUrlToIp(const std::string& host, char* ip) const
    	{
    		struct hostent* phost = NULL;
    
    		phost = gethostbyname(host.c_str());
    	    if (NULL == phost)
    	    {
    	    	LOG_ERROR << "HttpUrlToIp(): gethostbyname error : " << errno << " : "<< strerror(errno);
    	    	return false;
    	    	//LOG_SYSERR << "urlToIp(): gethostbyname error";
    	    }
    
    	    inet_ntop(phost->h_addrtype,  phost->h_addr, ip, 17);
    
    	    return true;
    	}
    
    	std::string domain() const
    	{
    		return getHttpUrlSubSeg(HOST);
    	}
    
    	std::string getHttpUrlSubSeg(HttpUrlMatch sub = HOST) const{ return m_smatch[sub]; }
    
    private:
    	std::string m_httpUrl;
    	std::vector<std::string> m_smatch;
    };
    
    
    class HttpRequest
    {
    public:
    	enum HttpRequestMethod{
    		GET = 0,
    		POST
    	};
    	HttpRequest(std::string httpUrl);
    	~HttpRequest();
    
    	void connect();
    	//void TEST(const std::string path,const std::string content);
    	void setRequestMethod(const std::string &method);
    	void setRequestProperty(const std::string &key, const std::string &value);
    	void setRequestBody(const std::string &content);
    
    	//void clear() { clearStream(); m_buffer.retrieveAll(); }
    	void clearStream() {m_stream.str("");}
    	std::string strStream() const { return m_stream.str(); };
    
    	int getResponseCode() const {
    		assert(m_haveHandleHead);
    		return m_code;
    	}
    
    	std::string getResponseProperty(const std::string& key) const {
    		assert(m_haveHandleHead);
    		return m_ackProperty.at(key);
    	}
    
    	std::string getResponseContent() {
    		assert(m_haveHandleHead);
    		return std::string(m_buffer.peek(), m_buffer.readableBytes());
    	}
    
    	void handleRead();
    	void uploadFile(const std::string& file, const std::string& contentEnd);
    	void downloadFile(const std::string& file);
    
    	void send(){
    		sockets::write(m_sockfd, strStream().c_str(), strStream().size());
    	}
    
    	void close(){ sockets::close(m_sockfd); }
    
    private:
        void SplitString(const std::string& s, std::vector<std::string>& v, const std::string& c);
    
      Buffer m_buffer;
    	HttpUrl m_httpUrl;
    	std::stringstream m_stream;
    	int m_code;
    	int m_sockfd;
    	bool m_haveHandleHead;
    	std::map<std::string, std::string> m_ackProperty;
    };
    
    #endif
    

    关于上传和下载

    HttpRequest::downloadFile(const std::string& file);
    下载直接在 handRead() 处理完应答头后 , 调用downloadFile() 存储在本地.
    HttpRequest::uploadFile(const std::string& file, const std::string& contentEnd)
    上传部分复杂一点,需要根据kBoundary 设置边界,如下.

    	off_t fileSize = FileSize(uploadFile);
    	LOG_DEBUG << "fileSize : " << fileSize;
    	std::stringstream content;
    	content << "--" + kBoundary << "\r\n";
    	content << "Content-Disposition: form-data; name=\"upload\"; filename=\"test.file\"\r\n";
    	content << "Content-Type: text/plain\r\n\r\n";
    
    	std::string contentEnd = "\r\n--" + kBoundary + "--\r\n";
    
    	HttpRequest upload("http://xxxxx/upload");
    	upload.setRequestMethod("POST");
    	upload.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + kBoundary);
    	upload.setRequestProperty("Cache-Control", "no-cache");
    	upload.setRequestProperty("Content-Length", std::to_string(fileSize + content.str().size() + contentEnd.size()));
    	upload.setRequestProperty("Connection", "close\r\n");
    	upload.setRequestBody(content.str());
    

    Cpp实现

    #include <string>
    #include <iostream>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/uio.h>  // readv
    #include <stdint.h>
    #include <endian.h>
    #include <unistd.h>
    #include <map>
    #include <fstream>
    
    #include "HttpRequest.hh"
    
    const std::map<std::string, int>::value_type init_value[] =
    {
    	std::map<std::string, int>::value_type( "GET", HttpRequest::GET),
    
    	std::map<std::string, int>::value_type( "POST", HttpRequest::POST)
    };
    
    const static std::map<std::string, int> kRequestMethodMap(init_value, init_value + (sizeof init_value / sizeof init_value[0]));
    
    static inline uint16_t hostToNetwork16(uint16_t host16)
    {
      return htobe16(host16);
    }
    
    
    int sockets::createSocket(sa_family_t family){
      // Call "socket()" to create a (family) socket of the specified type.
      // But also set it to have the 'close on exec' property (if we can)
    
    	int sock;
    
    	//CLOEXEC,即当调用exec()函数成功后,文件描述符会自动关闭。
    	//在以往的内核版本(2.6.23以前)中,需要调用 fcntl(fd, F_SETFD, FD_CLOEXEC) 来设置这个属性。
    	//而新版本(2.6.23开始)中,可以在调用open函数的时候,通过 flags 参数设置 CLOEXEC 功能,
    #ifdef SOCK_CLOEXEC
      sock = socket(family, SOCK_STREAM|SOCK_CLOEXEC, 0);
      if (sock != -1 || errno != EINVAL) return sock;
      // An "errno" of EINVAL likely means that the system wasn't happy with the SOCK_CLOEXEC; fall through and try again without it:
    #endif
    
      sock = socket(family, SOCK_STREAM, 0);
    
    #ifdef FD_CLOEXEC
      if (sock != -1) fcntl(sock, F_SETFD, FD_CLOEXEC);
    #endif
      return sock;
    }
    
    int sockets::connect(int sockfd, const struct sockaddr* addr)
    {
      return ::connect(sockfd, addr, sizeof(struct sockaddr));
    }
    
    void sockets::fromIpPort(const char* ip, uint16_t port,
                             struct sockaddr_in* addr)
    {
      addr->sin_family = AF_INET;
      addr->sin_port = hostToNetwork16(port);
      if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
      {
        LOG_SYSERR << "sockets::fromIpPort";
      }
    }
    
    ssize_t sockets::read(int sockfd, void *buf, size_t count)
    {
      return ::read(sockfd, buf, count);
    }
    
    ssize_t sockets::readv(int sockfd, const struct iovec *iov, int iovcnt)
    {
      return ::readv(sockfd, iov, iovcnt);
    }
    
    ssize_t sockets::write(int sockfd, const void *buf, size_t count)
    {
      return ::write(sockfd, buf, count);
    }
    
    void sockets::close(int sockfd)
    {
      if (::close(sockfd) < 0)
      {
        LOG_SYSERR << "sockets::close";
      }
    }
    
    void sockets::delaySecond(int sec)
    {
      struct  timeval tv;
      tv.tv_sec = sec;
      tv.tv_usec = 0;
      select(0, NULL, NULL, NULL, &tv);
    }
    
    
    
    InetAddress::InetAddress(std::string ip, uint16_t port)
    {
      ::bzero(&m_addr, sizeof m_addr);
      sockets::fromIpPort(ip.c_str(), port, &m_addr);
    }
    
    uint32_t InetAddress::ipNetEndian() const
    {
      assert(family() == AF_INET);
      return m_addr.sin_addr.s_addr;
    }
    
    
    HttpRequest::HttpRequest(std::string httpUrl)
      :m_httpUrl(httpUrl)
    {
    
    }
    
    HttpRequest::~HttpRequest()
    {
    
    }
    
    void HttpRequest::connect()
    {
      char ip[32] = {0};
      while(true)
      {
        struct hostent* phost = NULL;
    
        phost = gethostbyname(m_httpUrl.domain().c_str());
        if (NULL == phost)
        {
          LOG_ERROR << "HttpUrlToIp(): gethostbyname error : " << errno << " : "<< strerror(errno) << " continue.";
          sockets::delaySecond(1);
          continue;
        }
    
        inet_ntop(phost->h_addrtype,  phost->h_addr, ip, sizeof ip);
    
        LOG_DEBUG << "HttpRequest::Connector() gethostbyname Successful";
    
        InetAddress serverAddr = InetAddress(ip, 80);
    
        m_sockfd = sockets::createSocket(serverAddr.family());
        if(m_sockfd < 0) LOG_SYSERR << "HttpRequest::connect() : createSocket error";
        int ret = sockets::connect(m_sockfd, serverAddr.getSockAddr());
        LOG_DEBUG << "sockfd : " << m_sockfd << "sockets::connect ret : " << ret ;
        int savedErrno = (ret == 0) ? 0 : errno;
    
        switch (savedErrno)
        {
          case 0:
          case EINPROGRESS:
          case EINTR:
          case EISCONN:
            LOG_INFO << "HttpRequest::connect() sockfd : " << m_sockfd << " Successful";
            break;
          default :
            LOG_ERROR << "Connect Error ";
            sockets::delaySecond(1);
            continue;
        }
    
        break;
      }
    
      LOG_DEBUG << "HttpRequest::Connector() end";
    }
    
    void HttpRequest::setRequestMethod(const std::string &method)
    {
    	switch(kRequestMethodMap.at(method))
    	{
    		case HttpRequest::GET :
    			m_stream << "GET " << "/" << m_httpUrl.getHttpUrlSubSeg(HttpUrl::URI) << " HTTP/1.1\r\n";
    			LOG_DEBUG << m_stream.str().c_str();
    			break;
    		case HttpRequest::POST :
    			m_stream << "POST "  << "/" << m_httpUrl.getHttpUrlSubSeg(HttpUrl::URI) << " HTTP/1.1\r\n";
    			LOG_DEBUG << m_stream.str().c_str();
    			break;
    		default :
    			LOG_ERROR << "No such Method : " << method.c_str();
    			break;
    	}
    
    	m_stream << "Host: " << m_httpUrl.getHttpUrlSubSeg(HttpUrl::HOST) << "\r\n";
    }
    
    
    void HttpRequest::setRequestProperty(const std::string &key, const std::string &value)
    {
    	m_stream << key << ": " << value << "\r\n";
    }
    
    void HttpRequest::setRequestBody(const std::string &content)
    {
    	m_stream << content;
    }
    
    void HttpRequest::handleRead()
    {
    	assert(!m_haveHandleHead);
    	ssize_t nread = 0;
    	ssize_t writtenBytes = 0;
    
    	nread = sockets::read(m_sockfd, m_buffer.beginWrite(), kBufferSize);
    	if(nread < 0) LOG_SYSFATAL << "sockets::read";
    	m_buffer.hasWritten(nread);
    	LOG_TRACE << "sockets::read(): nread: " << nread << " remain: " << m_buffer.writableBytes();
    	size_t remain = kBufferSize - nread;
    	while(remain > 0)
    	{
    		size_t n = sockets::read(m_sockfd, m_buffer.beginWrite(), remain);
    		if(n < 0) LOG_SYSFATAL << "sockets::read";
    		m_buffer.hasWritten(n);
    		if(0 == n)
    		{
    			LOG_DEBUG << "sockets::read finish";
    			break;
    		}
    		remain = remain - n;
    	}
    	//std::cout << m_buffer.peek();
    
    	//for(int i = 0; i < nread; i++) printf("%02x%c",(unsigned char)buffer[i],i==nread - 1 ?'\n':' ');
    	//LOG_DEBUG << "handleRead Recv Response : \n" << m_buffer.peek();
    	int headsize = 0;
    	std::string line;
    	std::stringstream ss(m_buffer.peek());
    	std::vector<std::string> v;
    	getline(ss, line);
    	//LOG_DEBUG << line;
    	headsize += line.size() + 1;
    	SplitString(line, v, " ");
    	//for(int i = 0; i < v.size(); i++) std::cout << v[i] << std::endl;
    	m_code = std::stoi(v[1]);
    	if(v[1] != "200")
    	{
    	  LOG_ERROR << "Error Http Server Response : " << v[1].c_str();
    	}
    
    	do{
    		getline(ss, line);
    		headsize += line.size() + 1;  // + 1('\n')
    		if(!line.empty()) line.erase(line.end()-1); // remove '/r'
    		//LOG_DEBUG << line;
    		v.clear();
    		SplitString(line, v, ":");
    		if(v.size() == 2){
    			m_ackProperty[v[0]] = v[1].erase(0,v[1].find_first_not_of(" "));
    		}
    	}while(!line.empty());
    
    	LOG_DEBUG << "Http Head Size is " << headsize;
    	std::string res(m_buffer.peek(), headsize);
    	LOG_DEBUG << "Http Response :\n" << res;
    	m_buffer.retrieve(headsize);
    
    	m_haveHandleHead = true;
    
    }
    
    void HttpRequest::uploadFile(const std::string& file, const std::string& contentEnd)
    {
    
    	FILE* fp = fopen(file.c_str(), "rb");
    	if(fp == NULL)
    	{
    		LOG_SYSFATAL << "fopen() File :" << file.c_str() << " Errno";
    	}
    
    	bool isEnd = false;
    	ssize_t writtenBytes = 0;
    
    	assert(m_buffer.writableBytes() == Buffer::kInitialSize);
    
    	while(!isEnd)
    	{
    		ssize_t nread = fread(m_buffer.beginWrite(), 1, kBufferSize, fp);
    		m_buffer.hasWritten(nread);
    		while(m_buffer.writableBytes() > 0)
    		{
    			LOG_TRACE << "file read(): nread: " << nread << " remain: " << m_buffer.writableBytes();
    			size_t n = fread(m_buffer.beginWrite(), 1, m_buffer.writableBytes(), fp);
    			m_buffer.hasWritten(n);
    			if(0 == n)
    			{	int err = ferror(fp);
    				if(err)
    				{
    					fprintf(stderr, "fread failed : %s\n", strerror(err));
    				}
    				LOG_DEBUG << "sockets::read finish";
    				isEnd = true;
    				break;
    			}
    		}
    
    		ssize_t nwrite = sockets::write(m_sockfd, m_buffer.peek(), m_buffer.readableBytes());
    		if(nwrite < 0) LOG_SYSFATAL << "sockets::write";
    		writtenBytes += nwrite;
    		LOG_TRACE << "sockets::write nread " << m_buffer.readableBytes() << " nwrite " << nwrite << " writtenBytes " << writtenBytes;
    		m_buffer.retrieve(nwrite);
    	}
    
    	fclose(fp);
    
    	m_buffer.retrieveAll();
    
    	ssize_t n = sockets::write(m_sockfd, contentEnd.c_str(), contentEnd.size());
    	if(n < 0) LOG_SYSFATAL << "sockets::write";
    }
    
    void HttpRequest::downloadFile(const std::string& file)
    {
    	assert(m_haveHandleHead);
    
    	bool isEnd = false;
    	ssize_t nread = 0;
    	ssize_t writtenBytes = 0;
    	bool haveHandleHead = false;
    	bool isDownFile = false;
    
    	std::ofstream output(file, std::ios::binary);
    	if (!output.is_open()){ // 检查文件是否成功打开
    		LOG_SYSFATAL << "open file error" << file;
    	}
    
    	output.write(m_buffer.peek(), m_buffer.readableBytes());
    	writtenBytes += m_buffer.readableBytes();
    	m_buffer.retrieve(m_buffer.readableBytes());
    
    	LOG_DEBUG << "Content-Length : " << getResponseProperty("Content-Length");
    
    	while(!isEnd)
    	{
    		nread = sockets::read(m_sockfd, m_buffer.beginWrite(), kBufferSize);
    		if(nread < 0) LOG_SYSFATAL << "sockets::read";
    		m_buffer.hasWritten(nread);
    		LOG_TRACE << "sockets::read(): nread: " << nread << " remain: " << m_buffer.writableBytes() << " writtenBytes: " << writtenBytes;
    		size_t remain = kBufferSize - nread;
    		while(remain > 0)
    		{
    			size_t n = sockets::read(m_sockfd, m_buffer.beginWrite(), remain);
    			if(n < 0) LOG_SYSFATAL << "sockets::read";
    			m_buffer.hasWritten(n);
    			if(0 == n)
    			{
    				LOG_DEBUG << "sockets::read finish";
    				isEnd = true;
    				break;
    			}
    			remain = remain - n;
    		}
    
    		output.write(m_buffer.peek(), m_buffer.readableBytes());
    		writtenBytes += m_buffer.readableBytes();
    		m_buffer.retrieve(m_buffer.readableBytes());
    	}
    	LOG_DEBUG << " writtenBytes " << writtenBytes;
    
    	output.close();
    	sockets::close(m_sockfd);
    }
    
    void HttpRequest::SplitString(const std::string& s, std::vector<std::string>& v, const std::string& c)
    {
      std::string::size_type pos1, pos2;
      pos2 = s.find(c);
      pos1 = 0;
      while(std::string::npos != pos2)
      {
        v.push_back(s.substr(pos1, pos2-pos1));
    
        pos1 = pos2 + c.size();
        pos2 = s.find(c, pos1);
      }
      if(pos1 != s.length())
        v.push_back(s.substr(pos1));
    }
    
    

    关于源码中的Logger

    Logger部分使用的前面文章muduo日志库学习的Logger类,可以上我的github上下载,也可通过编辑器批量更改注释掉,或者换成std::cout不过需要在结尾多输出一个换行。

    使用示例

    下载图片

    #include "HttpRequest.hh"
    
    int main()
    {
    
      HttpRequest ImageReq("http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg");
      ImageReq.setRequestMethod("GET");
      ImageReq.setRequestProperty("Cache-Control", "no-cache");
      ImageReq.setRequestProperty("Content-Type", "application/octet-stream");
      ImageReq.setRequestProperty("Connection", "close\r\n");
    
      ImageReq.connect();
      ImageReq.send();
      ImageReq.handleRead();
      ImageReq.downloadFile("./test.jpg");
    
      return 0;
    }
    
    
    2018-10-23 20:24:07.257167 [DEBUG] [HttpRequest.hh:194] [detachHttpUrl] detachHttpUrl() url :http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257411 [DEBUG] [HttpRequest.hh:195] [detachHttpUrl] detachHttpUrl() host :img.zcool.cn
    2018-10-23 20:24:07.257561 [DEBUG] [HttpRequest.hh:196] [detachHttpUrl] detachHttpUrl() uri :community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257661 [DEBUG] [HttpRequest.hh:171] [HttpUrl] URL : http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257734 [DEBUG] [HttpRequest.cpp:178] [setRequestMethod] GET /community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg HTTP/1.1
    
    2018-10-23 20:24:08.192206 [DEBUG] [HttpRequest.cpp:142] [connect] HttpRequest::Connector() gethostbyname Successful
    2018-10-23 20:24:08.232474 [DEBUG] [HttpRequest.cpp:149] [connect] sockfd : 3sockets::connect ret : 0
    2018-10-23 20:24:08.233236 [INFO ] [HttpRequest.cpp:158] HttpRequest::connect() sockfd : 3 Successful
    2018-10-23 20:24:08.233453 [DEBUG] [HttpRequest.cpp:169] [connect] HttpRequest::Connector() end
    2018-10-23 20:24:08.257666 [TRACE] [HttpRequest.cpp:212] [handleRead] sockets::read(): nread: 4096 remain: 0
    2018-10-23 20:24:08.258490 [DEBUG] [HttpRequest.cpp:257] [handleRead] Http Head Size is 798
    2018-10-23 20:24:08.259139 [DEBUG] [HttpRequest.cpp:259] [handleRead] Http Response :
    HTTP/1.1 200 OK
    Server: Tengine
    Content-Type: image/jpeg
    Content-Length: 155984
    Connection: close
    Date: Tue, 23 Oct 2018 10:05:56 GMT
    x-oss-request-id: 5BCEF284EA872628A4BA7B32
    ETag: "3D2EABCF4B0809B38A154C5087E5ECD4"
    Last-Modified: Fri, 18 Mar 2016 03:09:12 GMT
    x-oss-object-type: Normal
    x-oss-hash-crc64ecma: 17069884749112719525
    x-oss-storage-class: Standard
    Expires: Thu, 18 Mar 2021 03:09:12 GMT
    Cache-Control: max-age=86400
    Via: cache10.l2cm9[0,304-0,H], cache35.l2cm9[37,0], cache1.cn683[0,200-0,H], cache1.cn683[2,0]
    Age: 8479
    Ali-Swift-Global-Savetime: 1538030855
    X-Cache: HIT TCP_HIT dirn:6:15817583
    X-Swift-SaveTime: Tue, 23 Oct 2018 10:18:38 GMT
    X-Swift-CacheTime: 85638
    Access-Control-Allow-Origin: *
    Timing-Allow-Origin: *
    EagleId: 71db884115402976359511426e
    
    
    2018-10-23 20:24:08.263882 [DEBUG] [HttpRequest.cpp:335] [downloadFile] Content-Length : 155984
    2018-10-23 20:24:08.264447 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 3298
    2018-10-23 20:24:08.264569 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 7394
    2018-10-23 20:24:08.264978 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 11490
    2018-10-23 20:24:08.265032 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 15586
    2018-10-23 20:24:08.265073 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 19682
    2018-10-23 20:24:08.265112 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 23778
    2018-10-23 20:24:08.265151 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 288 remain: 3808 writtenBytes: 27874
    2018-10-23 20:24:08.277661 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3432 remain: 664 writtenBytes: 31970
    2018-10-23 20:24:08.278022 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 784 remain: 3312 writtenBytes: 36066
    2018-10-23 20:24:08.278264 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2480 remain: 1616 writtenBytes: 40162
    2018-10-23 20:24:08.278580 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1280 remain: 2816 writtenBytes: 44258
    2018-10-23 20:24:08.278913 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1528 remain: 2568 writtenBytes: 48354
    2018-10-23 20:24:08.279254 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1776 remain: 2320 writtenBytes: 52450
    2018-10-23 20:24:08.279402 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 576 remain: 3520 writtenBytes: 56546
    2018-10-23 20:24:08.279920 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 824 remain: 3272 writtenBytes: 60642
    2018-10-23 20:24:08.280905 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 64738
    2018-10-23 20:24:08.280948 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2768 remain: 1328 writtenBytes: 68834
    2018-10-23 20:24:08.281217 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3016 remain: 1080 writtenBytes: 72930
    2018-10-23 20:24:08.281506 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3264 remain: 832 writtenBytes: 77026
    2018-10-23 20:24:08.281948 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2064 remain: 2032 writtenBytes: 81122
    2018-10-23 20:24:08.297685 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3760 remain: 336 writtenBytes: 85218
    2018-10-23 20:24:08.298170 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4008 remain: 88 writtenBytes: 89314
    2018-10-23 20:24:08.298362 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2808 remain: 1288 writtenBytes: 93410
    2018-10-23 20:24:08.298919 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3056 remain: 1040 writtenBytes: 97506
    2018-10-23 20:24:08.299576 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 101602
    2018-10-23 20:24:08.299641 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 656 remain: 3440 writtenBytes: 105698
    2018-10-23 20:24:08.300041 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2352 remain: 1744 writtenBytes: 109794
    2018-10-23 20:24:08.300479 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2600 remain: 1496 writtenBytes: 113890
    2018-10-23 20:24:08.301340 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2848 remain: 1248 writtenBytes: 117986
    2018-10-23 20:24:08.303384 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 122082
    2018-10-23 20:24:08.303646 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1920 remain: 2176 writtenBytes: 126178
    2018-10-23 20:24:08.303926 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 130274
    2018-10-23 20:24:08.304004 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 134370
    2018-10-23 20:24:08.304376 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 138466
    2018-10-23 20:24:08.304540 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 142562
    2018-10-23 20:24:08.304737 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 768 remain: 3328 writtenBytes: 146658
    2018-10-23 20:24:08.318544 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2464 remain: 1632 writtenBytes: 150754
    2018-10-23 20:24:08.320273 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1134 remain: 2962 writtenBytes: 154850
    2018-10-23 20:24:08.320374 [DEBUG] [HttpRequest.cpp:351] [downloadFile] sockets::read finish
    2018-10-23 20:24:08.320460 [DEBUG] [HttpRequest.cpp:362] [downloadFile]  writtenBytes 155984
    
    


    展开全文
  • 目录 一个linux下简单的纯C++实现Http请求类(GET,POST,上传,下载) Http协议简述 HttpRequest类设计 请求部分 接收部分 关于上传和下载 Cpp实现 关于源码中的Logger ...

    一个linux下简单的纯C++实现Http请求类(GET,POST,上传,下载)


    最近写了点关于Http上传下载文件相关的,于是今天整理下代码。

    Http协议简述
    HttpRequest类设计
    使用示例

    Http协议简述

    协议:网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流,就好比两台计算机交互的语言.

    HTTP协议:超文本传输协议(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。主要被用于在Web浏览器和网站服务器之间传递信息。 HTTP 是基于 TCP/IP 协议的应用层协议。默认使用80端口。最新版本是HTTP 2.0,目前是用最广泛的是HTTP 1.1。

    HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

    请求方法:
    HTTP/1.1协议中共定义了八种方法(有时也叫“动作”)来表明Request-URI指定的资源的不同操作方式:
    OPTIONS - 返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送'*'的请求来测试服务器的功能性。
    HEAD- 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新。
    GET - 向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在web app.中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
    POST - 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
    PUT - 向指定资源位置上传其最新内容。
    DELETE - 请求服务器删除Request-URI所标识的资源。
    TRACE- 回显服务器收到的请求,主要用于测试或诊断。
    CONNECT - HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
    PATCH - 用来将局部修改应用于某一资源,添加于规范RFC5789。
    方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed);当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
    HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。

    GET和POST请求的区别
    GET请求
    GET /books/?sex=man&name=Professional HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Connection: Keep-Alive
    注意最后有一行空行

    POST请求
    POST / HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 40
    Connection: Keep-Alive

    name=Professional%20Ajax&publisher=Wiley

    1、GET提交:请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,多个参数用&连接;例 如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0 %E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。

    2、POST提交:把提交的数据放置在是HTTP包的包体中。上文示例中红色字体标明的就是实际的传输数据

    HttpRequest类设计

    请求部分

    HttpRequest::HttpRequest() 通过传入Url构造HttpUrl类分离url域名及uri.
    HttpRequest::connect() 通过gethostbyname()获取域名ip,与80端口组成远端地址建立链接.
    HttpRequest::setRequestMethod() 设置请求方法,目前只添加了Get和Post请求.
    HttpRequest::setRequestProperty() 设置属性.
    HttpRequest::setRequestBody() 设置content.
    HttpRequest::send() 将设置的请求流发送出去.

    接收部分

    HttpRequest::handRead() 处理服务器应答头.
    HttpRequest::getResponseCode() handRead()后可以获取到应答code
    HttpRequest::getResponseProperty() handRead()后可以获取到对应的应答属性
    HttpRequest::getResponseContent() handRead()后可以获取到应答content

    #ifndef _HTTP_REQUEST_HH
    #define _HTTP_REQUEST_HH
    
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <algorithm>
    #include <vector>
    #include <string>
    #include <assert.h>
    #include <string.h>
    #include <sstream>
    #include <map>
    #include "Logger.hh"
    
    const size_t kBufferSize = 4096;
    
    /// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer
    ///
    /// @code
    /// +-------------------+------------------+------------------+
    /// | prependable bytes |  readable bytes  |  writable bytes  |
    /// |                   |     (CONTENT)    |                  |
    /// +-------------------+------------------+------------------+
    /// |                   |                  |                  |
    /// 0      <=      readerIndex   <=   writerIndex    <=     size
    /// @endcode
    class Buffer
    {
    public:
    
      static const size_t kCheapPrepend = 8;
      static const size_t kInitialSize = 4096;
    //public:
    
        explicit Buffer(size_t initialSize = kInitialSize)
        : m_buffer(kCheapPrepend + initialSize),
          m_readerIndex(kCheapPrepend),
          m_writerIndex(kCheapPrepend)
          {
            assert(readableBytes() == 0);
            assert(writableBytes() == initialSize);
            assert(prependableBytes() == kCheapPrepend);
          }
    
      size_t readableBytes() const
      { return m_writerIndex - m_readerIndex; }
    
      size_t writableBytes() const
      { return m_buffer.size() - m_writerIndex; }
    
      size_t prependableBytes() const
      { return m_readerIndex; }
    
      const char* peek() const
      { return begin() + m_readerIndex; }
    
      char* beginWrite()
      { return begin() + m_writerIndex; }
    
      void hasWritten(size_t len)
      {
        assert(len <= writableBytes());
        m_writerIndex += len;
      }
    
      void unwrite(size_t len)
      {
        assert(len <= readableBytes());
        m_writerIndex -= len;
      }
    
      // retrieve returns void, to prevent
      // string str(retrieve(readableBytes()), readableBytes());
      // the evaluation of two functions are unspecified
      void retrieve(size_t len)
      {
        assert(len <= readableBytes());
        if (len < readableBytes())
        {
          m_readerIndex += len;
        }
        else
        {
          retrieveAll();
        }
      }
    
      void retrieveAll()
      {
        m_readerIndex = kCheapPrepend;
        m_writerIndex = kCheapPrepend;
      }
    
    private:
      char* begin()
      {return &*m_buffer.begin(); }
    
      const char* begin() const
      {return &*m_buffer.begin(); }
    
    private:
        std::vector<char> m_buffer;
        size_t m_readerIndex;
        size_t m_writerIndex;
    };
    
    
    namespace sockets
    {
    
    /// Creates a non-blocking socket file descriptor,
    /// abort if any error.
    int createSocket(sa_family_t family);
    
    int  connect(int sockfd, const struct sockaddr* addr);
    ssize_t read(int sockfd, void *buf, size_t count);
    ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);
    ssize_t write(int sockfd, const void *buf, size_t count);
    void close(int sockfd);
    
    void fromIpPort(const char* ip, uint16_t port,
                    struct sockaddr_in* addr);
    
    int getSocketError(int sockfd);
    void delaySecond(int sec);
    //const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr)
    //const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr);
    
    }
    
    class InetAddress
    {
     public:
      /// Constructs an endpoint with given ip and port.
      /// @c ip should be "1.2.3.4"
      InetAddress(std::string ip, uint16_t port);
    
      /// Constructs an endpoint with given struct @c sockaddr_in
      /// Mostly used when accepting new connections
      explicit InetAddress(const struct sockaddr_in& addr)
        : m_addr(addr)
      { }
    
      sa_family_t family() const { return m_addr.sin_family; }
      //std::string toIp() const;
      //std::string toIpPort() const;
    
      const struct sockaddr* getSockAddr() const { return (struct sockaddr*)(&m_addr); }
    
      uint32_t ipNetEndian() const;
    
      // resolve hostname to IP address, not changing port or sin_family
      // return true on success.
      // thread safe
      // static bool resolve(StringArg hostname, StringArg* ip);
      // static std::vector<InetAddress> resolveAll(const char* hostname, uint16_t port = 0);
    
     private:
        struct sockaddr_in m_addr;
    
    };
    
    
    class HttpUrl
    {
    public:
        HttpUrl(std::string& httpUrl)
        :m_httpUrl(httpUrl),
         m_smatch(detachHttpUrl())
        {
            LOG_DEBUG << "URL : " << m_httpUrl;
        }
        ~HttpUrl(){};
    
        enum HttpUrlMatch
        {
            URL = 0,
            HOST = 1,
            URI = 2
        };
    
        std::vector<std::string> detachHttpUrl() const
        {
          std::vector<std::string> v;
          std::string::size_type pos1, pos2;
          pos2 = m_httpUrl.find('/');
          assert(std::string::npos != pos2);
          pos1 = pos2 + 2;
          pos2 = m_httpUrl.find('/', pos1);
          assert(std::string::npos != pos2);
          v.push_back(m_httpUrl);
          v.push_back(m_httpUrl.substr(pos1, pos2 - pos1));
          v.push_back(m_httpUrl.substr(pos2 + 1));
          LOG_DEBUG << "detachHttpUrl() url :" << v[0];
          LOG_DEBUG << "detachHttpUrl() host :" << v[1];
          LOG_DEBUG << "detachHttpUrl() uri :" << v[2];
          return v;
        }
    
        bool HttpUrlToIp(const std::string& host, char* ip) const
        {
            struct hostent* phost = NULL;
    
            phost = gethostbyname(host.c_str());
            if (NULL == phost)
            {
                LOG_ERROR << "HttpUrlToIp(): gethostbyname error : " << errno << " : "<< strerror(errno);
                return false;
                //LOG_SYSERR << "urlToIp(): gethostbyname error";
            }
    
            inet_ntop(phost->h_addrtype,  phost->h_addr, ip, 17);
    
            return true;
        }
    
        std::string domain() const
        {
            return getHttpUrlSubSeg(HOST);
        }
    
        std::string getHttpUrlSubSeg(HttpUrlMatch sub = HOST) const{ return m_smatch[sub]; }
    
    private:
        std::string m_httpUrl;
        std::vector<std::string> m_smatch;
    };
    
    
    class HttpRequest
    {
    public:
        enum HttpRequestMethod{
            GET = 0,
            POST
        };
        HttpRequest(std::string httpUrl);
        ~HttpRequest();
    
        void connect();
        //void TEST(const std::string path,const std::string content);
        void setRequestMethod(const std::string &method);
        void setRequestProperty(const std::string &key, const std::string &value);
        void setRequestBody(const std::string &content);
    
        //void clear() { clearStream(); m_buffer.retrieveAll(); }
        void clearStream() {m_stream.str("");}
        std::string strStream() const { return m_stream.str(); };
    
        int getResponseCode() const {
            assert(m_haveHandleHead);
            return m_code;
        }
    
        std::string getResponseProperty(const std::string& key) const {
            assert(m_haveHandleHead);
            return m_ackProperty.at(key);
        }
    
        std::string getResponseContent() {
            assert(m_haveHandleHead);
            return std::string(m_buffer.peek(), m_buffer.readableBytes());
        }
    
        void handleRead();
        void uploadFile(const std::string& file, const std::string& contentEnd);
        void downloadFile(const std::string& file);
    
        void send(){
            sockets::write(m_sockfd, strStream().c_str(), strStream().size());
        }
    
        void close(){ sockets::close(m_sockfd); }
    
    private:
        void SplitString(const std::string& s, std::vector<std::string>& v, const std::string& c);
    
      Buffer m_buffer;
        HttpUrl m_httpUrl;
        std::stringstream m_stream;
        int m_code;
        int m_sockfd;
        bool m_haveHandleHead;
        std::map<std::string, std::string> m_ackProperty;
    };
    
    #endif

    关于上传和下载

    HttpRequest::downloadFile(const std::string& file);
    下载直接在 handRead() 处理完应答头后 , 调用downloadFile() 存储在本地.
    HttpRequest::uploadFile(const std::string& file, const std::string& contentEnd)
    上传部分复杂一点,需要根据kBoundary 设置边界,如下.

        off_t fileSize = FileSize(uploadFile);
        LOG_DEBUG << "fileSize : " << fileSize;
        std::stringstream content;
        content << "--" + kBoundary << "\r\n";
        content << "Content-Disposition: form-data; name=\"upload\"; filename=\"test.file\"\r\n";
        content << "Content-Type: text/plain\r\n\r\n";
    
        std::string contentEnd = "\r\n--" + kBoundary + "--\r\n";
    
        HttpClient upload("http://xxxxx/upload");
        upload.setRequestMethod("POST");
        upload.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + kBoundary);
        upload.setRequestProperty("Cache-Control", "no-cache");
        upload.setRequestProperty("Content-Length", std::to_string(fileSize + content.str().size() + contentEnd.size()));
        upload.setRequestProperty("Connection", "close\r\n");
        upload.setRequestBody(content.str());

    Cpp实现

    #include <string>
    #include <iostream>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/uio.h>  // readv
    #include <stdint.h>
    #include <endian.h>
    #include <unistd.h>
    #include <map>
    #include <fstream>
    
    #include "HttpRequest.hh"
    
    const std::map<std::string, int>::value_type init_value[] =
    {
        std::map<std::string, int>::value_type( "GET", HttpRequest::GET),
    
        std::map<std::string, int>::value_type( "POST", HttpRequest::POST)
    };
    
    const static std::map<std::string, int> kRequestMethodMap(init_value, init_value + (sizeof init_value / sizeof init_value[0]));
    
    static inline uint16_t hostToNetwork16(uint16_t host16)
    {
      return htobe16(host16);
    }
    
    
    int sockets::createSocket(sa_family_t family){
      // Call "socket()" to create a (family) socket of the specified type.
      // But also set it to have the 'close on exec' property (if we can)
    
        int sock;
    
        //CLOEXEC,即当调用exec()函数成功后,文件描述符会自动关闭。
        //在以往的内核版本(2.6.23以前)中,需要调用 fcntl(fd, F_SETFD, FD_CLOEXEC) 来设置这个属性。
        //而新版本(2.6.23开始)中,可以在调用open函数的时候,通过 flags 参数设置 CLOEXEC 功能,
    #ifdef SOCK_CLOEXEC
      sock = socket(family, SOCK_STREAM|SOCK_CLOEXEC, 0);
      if (sock != -1 || errno != EINVAL) return sock;
      // An "errno" of EINVAL likely means that the system wasn't happy with the SOCK_CLOEXEC; fall through and try again without it:
    #endif
    
      sock = socket(family, SOCK_STREAM, 0);
    
    #ifdef FD_CLOEXEC
      if (sock != -1) fcntl(sock, F_SETFD, FD_CLOEXEC);
    #endif
      return sock;
    }
    
    int sockets::connect(int sockfd, const struct sockaddr* addr)
    {
      return ::connect(sockfd, addr, sizeof(struct sockaddr));
    }
    
    void sockets::fromIpPort(const char* ip, uint16_t port,
                             struct sockaddr_in* addr)
    {
      addr->sin_family = AF_INET;
      addr->sin_port = hostToNetwork16(port);
      if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
      {
        LOG_SYSERR << "sockets::fromIpPort";
      }
    }
    
    ssize_t sockets::read(int sockfd, void *buf, size_t count)
    {
      return ::read(sockfd, buf, count);
    }
    
    ssize_t sockets::readv(int sockfd, const struct iovec *iov, int iovcnt)
    {
      return ::readv(sockfd, iov, iovcnt);
    }
    
    ssize_t sockets::write(int sockfd, const void *buf, size_t count)
    {
      return ::write(sockfd, buf, count);
    }
    
    void sockets::close(int sockfd)
    {
      if (::close(sockfd) < 0)
      {
        LOG_SYSERR << "sockets::close";
      }
    }
    
    void sockets::delaySecond(int sec)
    {
      struct  timeval tv;
      tv.tv_sec = sec;
      tv.tv_usec = 0;
      select(0, NULL, NULL, NULL, &tv);
    }
    
    
    
    InetAddress::InetAddress(std::string ip, uint16_t port)
    {
      ::bzero(&m_addr, sizeof m_addr);
      sockets::fromIpPort(ip.c_str(), port, &m_addr);
    }
    
    uint32_t InetAddress::ipNetEndian() const
    {
      assert(family() == AF_INET);
      return m_addr.sin_addr.s_addr;
    }
    
    
    HttpRequest::HttpRequest(std::string httpUrl)
      :m_httpUrl(httpUrl)
    {
    
    }
    
    HttpRequest::~HttpRequest()
    {
    
    }
    
    void HttpRequest::connect()
    {
      char ip[32] = {0};
      while(true)
      {
        struct hostent* phost = NULL;
    
        phost = gethostbyname(m_httpUrl.domain().c_str());
        if (NULL == phost)
        {
          LOG_ERROR << "HttpUrlToIp(): gethostbyname error : " << errno << " : "<< strerror(errno) << " continue.";
          sockets::delaySecond(1);
          continue;
        }
    
        inet_ntop(phost->h_addrtype,  phost->h_addr, ip, sizeof ip);
    
        LOG_DEBUG << "HttpRequest::Connector() gethostbyname Successful";
    
        InetAddress serverAddr = InetAddress(ip, 80);
    
        m_sockfd = sockets::createSocket(serverAddr.family());
        if(m_sockfd < 0) LOG_SYSERR << "HttpRequest::connect() : createSocket error";
        int ret = sockets::connect(m_sockfd, serverAddr.getSockAddr());
        LOG_DEBUG << "sockfd : " << m_sockfd << "sockets::connect ret : " << ret ;
        int savedErrno = (ret == 0) ? 0 : errno;
    
        switch (savedErrno)
        {
          case 0:
          case EINPROGRESS:
          case EINTR:
          case EISCONN:
            LOG_INFO << "HttpRequest::connect() sockfd : " << m_sockfd << " Successful";
            break;
          default :
            LOG_ERROR << "Connect Error ";
            sockets::delaySecond(1);
            continue;
        }
    
        break;
      }
    
      LOG_DEBUG << "HttpRequest::Connector() end";
    }
    
    void HttpRequest::setRequestMethod(const std::string &method)
    {
        switch(kRequestMethodMap.at(method))
        {
            case HttpRequest::GET :
                m_stream << "GET " << "/" << m_httpUrl.getHttpUrlSubSeg(HttpUrl::URI) << " HTTP/1.1\r\n";
                LOG_DEBUG << m_stream.str().c_str();
                break;
            case HttpRequest::POST :
                m_stream << "POST "  << "/" << m_httpUrl.getHttpUrlSubSeg(HttpUrl::URI) << " HTTP/1.1\r\n";
                LOG_DEBUG << m_stream.str().c_str();
                break;
            default :
                LOG_ERROR << "No such Method : " << method.c_str();
                break;
        }
    
        m_stream << "Host: " << m_httpUrl.getHttpUrlSubSeg(HttpUrl::HOST) << "\r\n";
    }
    
    
    void HttpRequest::setRequestProperty(const std::string &key, const std::string &value)
    {
        m_stream << key << ": " << value << "\r\n";
    }
    
    void HttpRequest::setRequestBody(const std::string &content)
    {
        m_stream << content;
    }
    
    void HttpRequest::handleRead()
    {
        assert(!m_haveHandleHead);
        ssize_t nread = 0;
        ssize_t writtenBytes = 0;
    
        nread = sockets::read(m_sockfd, m_buffer.beginWrite(), kBufferSize);
        if(nread < 0) LOG_SYSFATAL << "sockets::read";
        m_buffer.hasWritten(nread);
        LOG_TRACE << "sockets::read(): nread: " << nread << " remain: " << m_buffer.writableBytes();
        size_t remain = kBufferSize - nread;
        while(remain > 0)
        {
            size_t n = sockets::read(m_sockfd, m_buffer.beginWrite(), remain);
            if(n < 0) LOG_SYSFATAL << "sockets::read";
            m_buffer.hasWritten(n);
            if(0 == n)
            {
                LOG_DEBUG << "sockets::read finish";
                break;
            }
            remain = remain - n;
        }
        //std::cout << m_buffer.peek();
    
        //for(int i = 0; i < nread; i++) printf("%02x%c",(unsigned char)buffer[i],i==nread - 1 ?'\n':' ');
        //LOG_DEBUG << "handleRead Recv Response : \n" << m_buffer.peek();
        int headsize = 0;
        std::string line;
        std::stringstream ss(m_buffer.peek());
        std::vector<std::string> v;
        getline(ss, line);
        //LOG_DEBUG << line;
        headsize += line.size() + 1;
        SplitString(line, v, " ");
        //for(int i = 0; i < v.size(); i++) std::cout << v[i] << std::endl;
        m_code = std::stoi(v[1]);
        if(v[1] != "200")
        {
          LOG_ERROR << "Error Http Server Response : " << v[1].c_str();
        }
    
        do{
            getline(ss, line);
            headsize += line.size() + 1;  // + 1('\n')
            if(!line.empty()) line.erase(line.end()-1); // remove '/r'
            //LOG_DEBUG << line;
            v.clear();
            SplitString(line, v, ":");
            if(v.size() == 2){
                m_ackProperty[v[0]] = v[1].erase(0,v[1].find_first_not_of(" "));
            }
        }while(!line.empty());
    
        LOG_DEBUG << "Http Head Size is " << headsize;
        std::string res(m_buffer.peek(), headsize);
        LOG_DEBUG << "Http Response :\n" << res;
        m_buffer.retrieve(headsize);
    
        m_haveHandleHead = true;
    
    }
    
    void HttpRequest::uploadFile(const std::string& file, const std::string& contentEnd)
    {
    
        FILE* fp = fopen(file.c_str(), "rb");
        if(fp == NULL)
        {
            LOG_SYSFATAL << "fopen() File :" << file.c_str() << " Errno";
        }
    
        bool isEnd = false;
        ssize_t writtenBytes = 0;
    
        assert(m_buffer.writableBytes() == Buffer::kInitialSize);
    
        while(!isEnd)
        {
            ssize_t nread = fread(m_buffer.beginWrite(), 1, kBufferSize, fp);
            m_buffer.hasWritten(nread);
            while(m_buffer.writableBytes() > 0)
            {
                LOG_TRACE << "file read(): nread: " << nread << " remain: " << m_buffer.writableBytes();
                size_t n = fread(m_buffer.beginWrite(), 1, m_buffer.writableBytes(), fp);
                m_buffer.hasWritten(n);
                if(0 == n)
                {   int err = ferror(fp);
                    if(err)
                    {
                        fprintf(stderr, "fread failed : %s\n", strerror(err));
                    }
                    LOG_DEBUG << "sockets::read finish";
                    isEnd = true;
                    break;
                }
            }
    
            ssize_t nwrite = sockets::write(m_sockfd, m_buffer.peek(), m_buffer.readableBytes());
            if(nwrite < 0) LOG_SYSFATAL << "sockets::write";
            writtenBytes += nwrite;
            LOG_TRACE << "sockets::write nread " << m_buffer.readableBytes() << " nwrite " << nwrite << " writtenBytes " << writtenBytes;
            m_buffer.retrieve(nwrite);
        }
    
        fclose(fp);
    
        m_buffer.retrieveAll();
    
        ssize_t n = sockets::write(m_sockfd, contentEnd.c_str(), contentEnd.size());
        if(n < 0) LOG_SYSFATAL << "sockets::write";
    }
    
    void HttpRequest::downloadFile(const std::string& file)
    {
        assert(m_haveHandleHead);
    
        bool isEnd = false;
        ssize_t nread = 0;
        ssize_t writtenBytes = 0;
        bool haveHandleHead = false;
        bool isDownFile = false;
    
        std::ofstream output(file, std::ios::binary);
        if (!output.is_open()){ // 检查文件是否成功打开
            LOG_SYSFATAL << "open file error" << file;
        }
    
        output.write(m_buffer.peek(), m_buffer.readableBytes());
        writtenBytes += m_buffer.readableBytes();
        m_buffer.retrieve(m_buffer.readableBytes());
    
        LOG_DEBUG << "Content-Length : " << getResponseProperty("Content-Length");
    
        while(!isEnd)
        {
            nread = sockets::read(m_sockfd, m_buffer.beginWrite(), kBufferSize);
            if(nread < 0) LOG_SYSFATAL << "sockets::read";
            m_buffer.hasWritten(nread);
            LOG_TRACE << "sockets::read(): nread: " << nread << " remain: " << m_buffer.writableBytes() << " writtenBytes: " << writtenBytes;
            size_t remain = kBufferSize - nread;
            while(remain > 0)
            {
                size_t n = sockets::read(m_sockfd, m_buffer.beginWrite(), remain);
                if(n < 0) LOG_SYSFATAL << "sockets::read";
                m_buffer.hasWritten(n);
                if(0 == n)
                {
                    LOG_DEBUG << "sockets::read finish";
                    isEnd = true;
                    break;
                }
                remain = remain - n;
            }
    
            output.write(m_buffer.peek(), m_buffer.readableBytes());
            writtenBytes += m_buffer.readableBytes();
            m_buffer.retrieve(m_buffer.readableBytes());
        }
        LOG_DEBUG << " writtenBytes " << writtenBytes;
    
        output.close();
        sockets::close(m_sockfd);
    }
    
    void HttpRequest::SplitString(const std::string& s, std::vector<std::string>& v, const std::string& c)
    {
      std::string::size_type pos1, pos2;
      pos2 = s.find(c);
      pos1 = 0;
      while(std::string::npos != pos2)
      {
        v.push_back(s.substr(pos1, pos2-pos1));
    
        pos1 = pos2 + c.size();
        pos2 = s.find(c, pos1);
      }
      if(pos1 != s.length())
        v.push_back(s.substr(pos1));
    }
    

    关于源码中的Logger

    Logger部分使用的前面文章的Logger类,可通过编辑器批量更改,注释掉,或复制第一篇异步日志的代码直接使用.

    使用示例

    下载图片

    #include "HttpRequest.hh"
    
    int main()
    {
    
      HttpRequest ImageReq("http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg");
      ImageReq.setRequestMethod("GET");
      ImageReq.setRequestProperty("Cache-Control", "no-cache");
      ImageReq.setRequestProperty("Content-Type", "application/octet-stream");
      ImageReq.setRequestProperty("Connection", "close\r\n");
    
      ImageReq.connect();
      ImageReq.send();
      ImageReq.handleRead();
      ImageReq.downloadFile("./test.jpg");
    
      return 0;
    }
    
    2018-10-23 20:24:07.257167 [DEBUG] [HttpRequest.hh:194] [detachHttpUrl] detachHttpUrl() url :http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257411 [DEBUG] [HttpRequest.hh:195] [detachHttpUrl] detachHttpUrl() host :img.zcool.cn
    2018-10-23 20:24:07.257561 [DEBUG] [HttpRequest.hh:196] [detachHttpUrl] detachHttpUrl() uri :community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257661 [DEBUG] [HttpRequest.hh:171] [HttpUrl] URL : http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257734 [DEBUG] [HttpRequest.cpp:178] [setRequestMethod] GET /community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg HTTP/1.1
    
    2018-10-23 20:24:08.192206 [DEBUG] [HttpRequest.cpp:142] [connect] HttpRequest::Connector() gethostbyname Successful
    2018-10-23 20:24:08.232474 [DEBUG] [HttpRequest.cpp:149] [connect] sockfd : 3sockets::connect ret : 0
    2018-10-23 20:24:08.233236 [INFO ] [HttpRequest.cpp:158] HttpRequest::connect() sockfd : 3 Successful
    2018-10-23 20:24:08.233453 [DEBUG] [HttpRequest.cpp:169] [connect] HttpRequest::Connector() end
    2018-10-23 20:24:08.257666 [TRACE] [HttpRequest.cpp:212] [handleRead] sockets::read(): nread: 4096 remain: 0
    2018-10-23 20:24:08.258490 [DEBUG] [HttpRequest.cpp:257] [handleRead] Http Head Size is 798
    2018-10-23 20:24:08.259139 [DEBUG] [HttpRequest.cpp:259] [handleRead] Http Response :
    HTTP/1.1 200 OK
    Server: Tengine
    Content-Type: image/jpeg
    Content-Length: 155984
    Connection: close
    Date: Tue, 23 Oct 2018 10:05:56 GMT
    x-oss-request-id: 5BCEF284EA872628A4BA7B32
    ETag: "3D2EABCF4B0809B38A154C5087E5ECD4"
    Last-Modified: Fri, 18 Mar 2016 03:09:12 GMT
    x-oss-object-type: Normal
    x-oss-hash-crc64ecma: 17069884749112719525
    x-oss-storage-class: Standard
    Expires: Thu, 18 Mar 2021 03:09:12 GMT
    Cache-Control: max-age=86400
    Via: cache10.l2cm9[0,304-0,H], cache35.l2cm9[37,0], cache1.cn683[0,200-0,H], cache1.cn683[2,0]
    Age: 8479
    Ali-Swift-Global-Savetime: 1538030855
    X-Cache: HIT TCP_HIT dirn:6:15817583
    X-Swift-SaveTime: Tue, 23 Oct 2018 10:18:38 GMT
    X-Swift-CacheTime: 85638
    Access-Control-Allow-Origin: *
    Timing-Allow-Origin: *
    EagleId: 71db884115402976359511426e
    
    
    2018-10-23 20:24:08.263882 [DEBUG] [HttpRequest.cpp:335] [downloadFile] Content-Length : 155984
    2018-10-23 20:24:08.264447 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 3298
    2018-10-23 20:24:08.264569 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 7394
    2018-10-23 20:24:08.264978 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 11490
    2018-10-23 20:24:08.265032 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 15586
    2018-10-23 20:24:08.265073 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 19682
    2018-10-23 20:24:08.265112 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 23778
    2018-10-23 20:24:08.265151 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 288 remain: 3808 writtenBytes: 27874
    2018-10-23 20:24:08.277661 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3432 remain: 664 writtenBytes: 31970
    2018-10-23 20:24:08.278022 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 784 remain: 3312 writtenBytes: 36066
    2018-10-23 20:24:08.278264 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2480 remain: 1616 writtenBytes: 40162
    2018-10-23 20:24:08.278580 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1280 remain: 2816 writtenBytes: 44258
    2018-10-23 20:24:08.278913 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1528 remain: 2568 writtenBytes: 48354
    2018-10-23 20:24:08.279254 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1776 remain: 2320 writtenBytes: 52450
    2018-10-23 20:24:08.279402 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 576 remain: 3520 writtenBytes: 56546
    2018-10-23 20:24:08.279920 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 824 remain: 3272 writtenBytes: 60642
    2018-10-23 20:24:08.280905 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 64738
    2018-10-23 20:24:08.280948 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2768 remain: 1328 writtenBytes: 68834
    2018-10-23 20:24:08.281217 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3016 remain: 1080 writtenBytes: 72930
    2018-10-23 20:24:08.281506 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3264 remain: 832 writtenBytes: 77026
    2018-10-23 20:24:08.281948 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2064 remain: 2032 writtenBytes: 81122
    2018-10-23 20:24:08.297685 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3760 remain: 336 writtenBytes: 85218
    2018-10-23 20:24:08.298170 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4008 remain: 88 writtenBytes: 89314
    2018-10-23 20:24:08.298362 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2808 remain: 1288 writtenBytes: 93410
    2018-10-23 20:24:08.298919 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 3056 remain: 1040 writtenBytes: 97506
    2018-10-23 20:24:08.299576 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 101602
    2018-10-23 20:24:08.299641 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 656 remain: 3440 writtenBytes: 105698
    2018-10-23 20:24:08.300041 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2352 remain: 1744 writtenBytes: 109794
    2018-10-23 20:24:08.300479 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2600 remain: 1496 writtenBytes: 113890
    2018-10-23 20:24:08.301340 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2848 remain: 1248 writtenBytes: 117986
    2018-10-23 20:24:08.303384 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 122082
    2018-10-23 20:24:08.303646 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1920 remain: 2176 writtenBytes: 126178
    2018-10-23 20:24:08.303926 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 130274
    2018-10-23 20:24:08.304004 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 134370
    2018-10-23 20:24:08.304376 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 138466
    2018-10-23 20:24:08.304540 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 4096 remain: 0 writtenBytes: 142562
    2018-10-23 20:24:08.304737 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 768 remain: 3328 writtenBytes: 146658
    2018-10-23 20:24:08.318544 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 2464 remain: 1632 writtenBytes: 150754
    2018-10-23 20:24:08.320273 [TRACE] [HttpRequest.cpp:342] [downloadFile] sockets::read(): nread: 1134 remain: 2962 writtenBytes: 154850
    2018-10-23 20:24:08.320374 [DEBUG] [HttpRequest.cpp:351] [downloadFile] sockets::read finish
    2018-10-23 20:24:08.320460 [DEBUG] [HttpRequest.cpp:362] [downloadFile]  writtenBytes 155984
    

    1384555-20181023232918348-168535015.jpg


    转载于:https://www.cnblogs.com/ailumiyana/p/9839170.html

    展开全文
  • linux下C++实现Http请求类(GET,POST,上传,下载) Http协议简述 协议:网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能...

    linux下C++实现Http请求类(GET,POST,上传,下载)

    Http协议简述

    协议:网络协议的简称,网络协议是通信计算机双方必须共同遵从的一组约定。如怎么样建立连接、怎么样互相识别等。只有遵守这个约定,计算机之间才能相互通信交流,就好比两台计算机交互的语言.

    HTTP协议:超文本传输协议(HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。主要被用于在Web浏览器和网站服务器之间传递信息。 HTTP 是基于 TCP/IP 协议的应用层协议。默认使用80端口。最新版本是HTTP 2.0,目前是用最广泛的是HTTP 1.1。

    HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。

    请求方法:
    HTTP/1.1协议中共定义了八种方法(有时也叫“动作”)来表明Request-URI指定的资源的不同操作方式:
    OPTIONS - 返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送’*'的请求来测试服务器的功能性。
    HEAD- 向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。该方法常用于测试超链接的有效性,是否可以访问,以及最近是否更新。
    GET - 向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在web app.中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
    POST - 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
    PUT - 向指定资源位置上传其最新内容。
    DELETE - 请求服务器删除Request-URI所标识的资源。
    TRACE- 回显服务器收到的请求,主要用于测试或诊断。
    CONNECT - HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
    PATCH - 用来将局部修改应用于某一资源,添加于规范RFC5789。
    方法名称是区分大小写的。当某个请求所针对的资源不支持对应的请求方法的时候,服务器应当返回状态码405(Method Not Allowed);当服务器不认识或者不支持对应的请求方法的时候,应当返回状态码501(Not Implemented)。
    HTTP服务器至少应该实现GET和HEAD方法,其他方法都是可选的。此外,除了上述方法,特定的HTTP服务器还能够扩展自定义的方法。

    GET和POST请求的区别
    GET请求
    GET /books/?sex=man&name=Professional HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Connection: Keep-Alive
    注意最后有一行空行

    POST请求
    POST / HTTP/1.1
    Host: www.wrox.com
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
    Gecko/20050225 Firefox/1.0.1
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 40
    Connection: Keep-Alive

    name=Professional%20Ajax&publisher=Wiley

    1、GET提交:请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,多个参数用&连接;例 如:login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0 %E5%A5%BD。如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,得出如: %E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。

    2、POST提交:把提交的数据放置在是HTTP包的包体中。上文示例中红色字体标明的就是实际的传输数据

    HttpRequest类设计
    请求部分
    HttpRequest::HttpRequest() 通过传入Url构造HttpUrl类分离url域名及uri.
    HttpRequest::connect() 通过gethostbyname()获取域名ip,与80端口组成远端地址建立链接.
    HttpRequest::setRequestMethod() 设置请求方法,目前只添加了Get和Post请求.
    HttpRequest::setRequestProperty() 设置属性.
    HttpRequest::setRequestBody() 设置content.
    HttpRequest::send() 将设置的请求流发送出去.

    接收部分
    HttpRequest::handRead() 处理服务器应答头.
    HttpRequest::getResponseCode() handRead()后可以获取到应答code
    HttpRequest::getResponseProperty() handRead()后可以获取到对应的应答属性
    HttpRequest::getResponseContent() handRead()后可以获取到应答content

    #ifndef _HTTP_REQUEST_HH
    #define _HTTP_REQUEST_HH
    
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <algorithm>
    #include <vector>
    #include <string>
    #include <assert.h>
    #include <string.h>
    #include <sstream>
    #include <map>
    #include "Logger.hh"
    
    const size_t kBufferSize = 4096;
    
    /// A buffer class modeled after org.jboss.netty.buffer.ChannelBuffer
    ///
    /// @code
    /// +-------------------+------------------+------------------+
    /// | prependable bytes |  readable bytes  |  writable bytes  |
    /// |                   |     (CONTENT)    |                  |
    /// +-------------------+------------------+------------------+
    /// |                   |                  |                  |
    /// 0      <=      readerIndex   <=   writerIndex    <=     size
    /// @endcode
    class Buffer
    {
    public:
    
      static const size_t kCheapPrepend = 8;
      static const size_t kInitialSize = 4096;
    //public:
        explicit Buffer(size_t initialSize = kInitialSize)
        : m_buffer(kCheapPrepend + initialSize),
          m_readerIndex(kCheapPrepend),
          m_writerIndex(kCheapPrepend)
          {
            assert(readableBytes() == 0);
            assert(writableBytes() == initialSize);
            assert(prependableBytes() == kCheapPrepend);
          }
    
      size_t readableBytes() const
      { return m_writerIndex - m_readerIndex; }
    
      size_t writableBytes() const
      { return m_buffer.size() - m_writerIndex; }
    
      size_t prependableBytes() const
      { return m_readerIndex; }
    
      const char* peek() const
      { return begin() + m_readerIndex; }
    
      char* beginWrite()
      { return begin() + m_writerIndex; }
    
      void hasWritten(size_t len)
      {
        assert(len <= writableBytes());
        m_writerIndex += len;
      }
    
      void unwrite(size_t len)
      {
        assert(len <= readableBytes());
        m_writerIndex -= len;
      }
    
      // retrieve returns void, to prevent
      // string str(retrieve(readableBytes()), readableBytes());
      // the evaluation of two functions are unspecified
      void retrieve(size_t len)
      {
        assert(len <= readableBytes());
        if (len < readableBytes())
        {
          m_readerIndex += len;
        }
        else
        {
          retrieveAll();
        }
      }
    
      void retrieveAll()
      {
        m_readerIndex = kCheapPrepend;
        m_writerIndex = kCheapPrepend;
      }
    
    private:
      char* begin()
      {return &*m_buffer.begin(); }
    
      const char* begin() const
      {return &*m_buffer.begin(); }
    
    private:
        std::vector<char> m_buffer;
        size_t m_readerIndex;
        size_t m_writerIndex;
    };
    
    
    namespace sockets
    {
    
    /// Creates a non-blocking socket file descriptor,
    /// abort if any error.
    int createSocket(sa_family_t family);
    
    int  connect(int sockfd, const struct sockaddr* addr);
    ssize_t read(int sockfd, void *buf, size_t count);
    ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);
    ssize_t write(int sockfd, const void *buf, size_t count);
    void close(int sockfd);
    
    void fromIpPort(const char* ip, uint16_t port,
                    struct sockaddr_in* addr);
    
    int getSocketError(int sockfd);
    void delaySecond(int sec);
    //const struct sockaddr* sockaddr_cast(const struct sockaddr_in* addr)
    //const struct sockaddr_in* sockaddr_in_cast(const struct sockaddr* addr);
    
    }
    
    class InetAddress
    {
     public:
      /// Constructs an endpoint with given ip and port.
      /// @c ip should be "1.2.3.4"
      InetAddress(std::string ip, uint16_t port);
    
      /// Constructs an endpoint with given struct @c sockaddr_in
      /// Mostly used when accepting new connections
      explicit InetAddress(const struct sockaddr_in& addr)
        : m_addr(addr)
      { }
    
      sa_family_t family() const { return m_addr.sin_family; }
      //std::string toIp() const;
      //std::string toIpPort() const;
    
      const struct sockaddr* getSockAddr() const { return (struct sockaddr*)(&m_addr); }
    
      uint32_t ipNetEndian() const;
    
      // resolve hostname to IP address, not changing port or sin_family
      // return true on success.
      // thread safe
      // static bool resolve(StringArg hostname, StringArg* ip);
      // static std::vector<InetAddress> resolveAll(const char* hostname, uint16_t port = 0);
    
     private:
        struct sockaddr_in m_addr;
    
    };
    
    
    class HttpUrl
    {
    public:
        HttpUrl(std::string& httpUrl)
        :m_httpUrl(httpUrl),
         m_smatch(detachHttpUrl())
        {
            LOG_DEBUG << "URL : " << m_httpUrl;
        }
        ~HttpUrl(){};
    
        enum HttpUrlMatch
        {
            URL = 0,
            HOST = 1,
            URI = 2
        };
    
        std::vector<std::string> detachHttpUrl() const
        {
          std::vector<std::string> v;
          std::string::size_type pos1, pos2;
          pos2 = m_httpUrl.find('/');
          assert(std::string::npos != pos2);
          pos1 = pos2 + 2;
          pos2 = m_httpUrl.find('/', pos1);
          assert(std::string::npos != pos2);
          v.push_back(m_httpUrl);
          v.push_back(m_httpUrl.substr(pos1, pos2 - pos1));
          v.push_back(m_httpUrl.substr(pos2 + 1));
          LOG_DEBUG << "detachHttpUrl() url :" << v[0];
          LOG_DEBUG << "detachHttpUrl() host :" << v[1];
          LOG_DEBUG << "detachHttpUrl() uri :" << v[2];
          return v;
        }
    
        bool HttpUrlToIp(const std::string& host, char* ip) const
        {
            struct hostent* phost = NULL;
    
            phost = gethostbyname(host.c_str());
            if (NULL == phost)
            {
                LOG_ERROR << "HttpUrlToIp(): gethostbyname error : " << errno << " : "<< strerror(errno);
                return false;
                //LOG_SYSERR << "urlToIp(): gethostbyname error";
            }
    
            inet_ntop(phost->h_addrtype,  phost->h_addr, ip, 17);
    
            return true;
        }
    
        std::string domain() const
        {
            return getHttpUrlSubSeg(HOST);
        }
    
        std::string getHttpUrlSubSeg(HttpUrlMatch sub = HOST) const{ return m_smatch[sub]; }
    
    private:
        std::string m_httpUrl;
        std::vector<std::string> m_smatch;
    };
    
    
    class HttpRequest
    {
    public:
        enum HttpRequestMethod{
            GET = 0,
            POST
        };
        HttpRequest(std::string httpUrl);
        ~HttpRequest();
    
        void connect();
        //void TEST(const std::string path,const std::string content);
        void setRequestMethod(const std::string &method);
        void setRequestProperty(const std::string &key, const std::string &value);
        void setRequestBody(const std::string &content);
    
        //void clear() { clearStream(); m_buffer.retrieveAll(); }
        void clearStream() {m_stream.str("");}
        std::string strStream() const { return m_stream.str(); };
    
        int getResponseCode() const {
            assert(m_haveHandleHead);
            return m_code;
        }
    
        std::string getResponseProperty(const std::string& key) const {
            assert(m_haveHandleHead);
            return m_ackProperty.at(key);
        }
    
        std::string getResponseContent() {
            assert(m_haveHandleHead);
            return std::string(m_buffer.peek(), m_buffer.readableBytes());
        }
    
        void handleRead();
        void uploadFile(const std::string& file, const std::string& contentEnd);
        void downloadFile(const std::string& file);
    
        void send(){
            sockets::write(m_sockfd, strStream().c_str(), strStream().size());
        }
    
        void close(){ sockets::close(m_sockfd); }
    
    private:
        void SplitString(const std::string& s, std::vector<std::string>& v, const std::string& c);
    
      Buffer m_buffer;
        HttpUrl m_httpUrl;
        std::stringstream m_stream;
        int m_code;
        int m_sockfd;
        bool m_haveHandleHead;
        std::map<std::string, std::string> m_ackProperty;
    };
    
    #endif
    

    关于上传和下载
    HttpRequest::downloadFile(const std::string& file);
    下载直接在 handRead() 处理完应答头后 , 调用downloadFile() 存储在本地.
    HttpRequest::uploadFile(const std::string& file, const std::string& contentEnd)
    上传部分复杂一点,需要根据kBoundary 设置边界,如下.

        off_t fileSize = FileSize(uploadFile);
        LOG_DEBUG << "fileSize : " << fileSize;
        std::stringstream content;
        content << "--" + kBoundary << "\r\n";
        content << "Content-Disposition: form-data; name=\"upload\"; filename=\"test.file\"\r\n";
        content << "Content-Type: text/plain\r\n\r\n";
    
        std::string contentEnd = "\r\n--" + kBoundary + "--\r\n";
    
        HttpClient upload("http://xxxxx/upload");
        upload.setRequestMethod("POST");
        upload.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + kBoundary);
        upload.setRequestProperty("Cache-Control", "no-cache");
        upload.setRequestProperty("Content-Length", std::to_string(fileSize + content.str().size() + contentEnd.size()));
        upload.setRequestProperty("Connection", "close\r\n");
        upload.setRequestBody(content.str());
    

    Cpp实现

    #include <string>
    #include <iostream>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/uio.h>  // readv
    #include <stdint.h>
    #include <endian.h>
    #include <unistd.h>
    #include <map>
    #include <fstream>
    
    #include "HttpRequest.h"
    
    const std::map<std::string, int>::value_type init_value[] =
    {
        std::map<std::string, int>::value_type( "GET", HttpRequest::GET),
    
        std::map<std::string, int>::value_type( "POST", HttpRequest::POST)
    };
    
    const static std::map<std::string, int> kRequestMethodMap(init_value, init_value + (sizeof init_value / sizeof init_value[0]));
    
    static inline uint16_t hostToNetwork16(uint16_t host16)
    {
      return htobe16(host16);
    }
    
    
    int sockets::createSocket(sa_family_t family){
      // Call "socket()" to create a (family) socket of the specified type.
      // But also set it to have the 'close on exec' property (if we can)
    
        int sock;
    
        //CLOEXEC,即当调用exec()函数成功后,文件描述符会自动关闭。
        //在以往的内核版本(2.6.23以前)中,需要调用 fcntl(fd, F_SETFD, FD_CLOEXEC) 来设置这个属性。
        //而新版本(2.6.23开始)中,可以在调用open函数的时候,通过 flags 参数设置 CLOEXEC 功能,
    #ifdef SOCK_CLOEXEC
      sock = socket(family, SOCK_STREAM|SOCK_CLOEXEC, 0);
      if (sock != -1 || errno != EINVAL) return sock;
      // An "errno" of EINVAL likely means that the system wasn't happy with the SOCK_CLOEXEC; fall through and try again without it:
    #endif
    
      sock = socket(family, SOCK_STREAM, 0);
    
    #ifdef FD_CLOEXEC
      if (sock != -1) fcntl(sock, F_SETFD, FD_CLOEXEC);
    #endif
      return sock;
    }
    
    int sockets::connect(int sockfd, const struct sockaddr* addr)
    {
      return ::connect(sockfd, addr, sizeof(struct sockaddr));
    }
    
    void sockets::fromIpPort(const char* ip, uint16_t port,
                             struct sockaddr_in* addr)
    {
      addr->sin_family = AF_INET;
      addr->sin_port = hostToNetwork16(port);
      if (::inet_pton(AF_INET, ip, &addr->sin_addr) <= 0)
      {
        LOG_SYSERR << "sockets::fromIpPort";
      }
    }
    
    ssize_t sockets::read(int sockfd, void *buf, size_t count)
    {
      return ::read(sockfd, buf, count);
    }
    
    ssize_t sockets::readv(int sockfd, const struct iovec *iov, int iovcnt)
    {
      return ::readv(sockfd, iov, iovcnt);
    }
    
    ssize_t sockets::write(int sockfd, const void *buf, size_t count)
    {
      return ::write(sockfd, buf, count);
    }
    
    void sockets::close(int sockfd)
    {
      if (::close(sockfd) < 0)
      {
        LOG_SYSERR << "sockets::close";
      }
    }
    
    void sockets::delaySecond(int sec)
    {
      struct  timeval tv;
      tv.tv_sec = sec;
      tv.tv_usec = 0;
      select(0, NULL, NULL, NULL, &tv);
    }
    
    
    
    InetAddress::InetAddress(std::string ip, uint16_t port)
    {
      ::bzero(&m_addr, sizeof m_addr);
      sockets::fromIpPort(ip.c_str(), port, &m_addr);
    }
    
    uint32_t InetAddress::ipNetEndian() const
    {
      assert(family() == AF_INET);
      return m_addr.sin_addr.s_addr;
    }
    
    
    HttpRequest::HttpRequest(std::string httpUrl)
      :m_httpUrl(httpUrl)
    {
    
    }
    
    HttpRequest::~HttpRequest()
    {
    
    }
    
    void HttpRequest::connect()
    {
      char ip[32] = {0};
      while(true)
      {
        struct hostent* phost = NULL;
    
        phost = gethostbyname(m_httpUrl.domain().c_str());
        if (NULL == phost)
        {
          LOG_ERROR << "HttpUrlToIp(): gethostbyname error : " << errno << " : "<< strerror(errno) << " continue.";
          sockets::delaySecond(1);
          continue;
        }
    
        inet_ntop(phost->h_addrtype,  phost->h_addr, ip, sizeof ip);
    
        LOG_DEBUG << "HttpRequest::Connector() gethostbyname Successful";
    
        InetAddress serverAddr = InetAddress(ip, 80);
    
        m_sockfd = sockets::createSocket(serverAddr.family());
        if(m_sockfd < 0) LOG_SYSERR << "HttpRequest::connect() : createSocket error";
        int ret = sockets::connect(m_sockfd, serverAddr.getSockAddr());
        LOG_DEBUG << "sockfd : " << m_sockfd << "sockets::connect ret : " << ret ;
        int savedErrno = (ret == 0) ? 0 : errno;
    
        switch (savedErrno)
        {
          case 0:
          case EINPROGRESS:
          case EINTR:
          case EISCONN:
            LOG_INFO << "HttpRequest::connect() sockfd : " << m_sockfd << " Successful";
            break;
          default :
            LOG_ERROR << "Connect Error ";
            sockets::delaySecond(1);
            continue;
        }
    
        break;
      }
    
      LOG_DEBUG << "HttpRequest::Connector() end";
    }
    
    void HttpRequest::setRequestMethod(const std::string &method)
    {
        switch(kRequestMethodMap.at(method))
        {
            case HttpRequest::GET :
                m_stream << "GET " << "/" << m_httpUrl.getHttpUrlSubSeg(HttpUrl::URI) << " HTTP/1.1\r\n";
                LOG_DEBUG << m_stream.str().c_str();
                break;
            case HttpRequest::POST :
                m_stream << "POST "  << "/" << m_httpUrl.getHttpUrlSubSeg(HttpUrl::URI) << " HTTP/1.1\r\n";
                LOG_DEBUG << m_stream.str().c_str();
                break;
            default :
                LOG_ERROR << "No such Method : " << method.c_str();
                break;
        }
    
        m_stream << "Host: " << m_httpUrl.getHttpUrlSubSeg(HttpUrl::HOST) << "\r\n";
    }
    
    
    void HttpRequest::setRequestProperty(const std::string &key, const std::string &value)
    {
        m_stream << key << ": " << value << "\r\n";
    }
    
    void HttpRequest::setRequestBody(const std::string &content)
    {
        m_stream << content;
    }
    
    void HttpRequest::handleRead()
    {
        assert(!m_haveHandleHead);
        ssize_t nread = 0;
        ssize_t writtenBytes = 0;
    
        nread = sockets::read(m_sockfd, m_buffer.beginWrite(), kBufferSize);
        if(nread < 0) LOG_SYSFATAL << "sockets::read";
        m_buffer.hasWritten(nread);
        LOG_TRACE << "sockets::read(): nread: " << nread << " remain: " << m_buffer.writableBytes();
        size_t remain = kBufferSize - nread;
        while(remain > 0)
        {
            size_t n = sockets::read(m_sockfd, m_buffer.beginWrite(), remain);
            if(n < 0) LOG_SYSFATAL << "sockets::read";
            m_buffer.hasWritten(n);
            if(0 == n)
            {
                LOG_DEBUG << "sockets::read finish";
                break;
            }
            remain = remain - n;
        }
        //std::cout << m_buffer.peek();
    
        //for(int i = 0; i < nread; i++) printf("%02x%c",(unsigned char)buffer[i],i==nread - 1 ?'\n':' ');
        //LOG_DEBUG << "handleRead Recv Response : \n" << m_buffer.peek();
        int headsize = 0;
        std::string line;
        std::stringstream ss(m_buffer.peek());
        std::vector<std::string> v;
        getline(ss, line);
        //LOG_DEBUG << line;
        headsize += line.size() + 1;
        SplitString(line, v, " ");
        //for(int i = 0; i < v.size(); i++) std::cout << v[i] << std::endl;
        m_code = std::stoi(v[1]);
        if(v[1] != "200")
        {
          LOG_ERROR << "Error Http Server Response : " << v[1].c_str();
        }
    
        do{
            getline(ss, line);
            headsize += line.size() + 1;  // + 1('\n')
            if(!line.empty()) line.erase(line.end()-1); // remove '/r'
            //LOG_DEBUG << line;
            v.clear();
            SplitString(line, v, ":");
            if(v.size() == 2){
                m_ackProperty[v[0]] = v[1].erase(0,v[1].find_first_not_of(" "));
            }
        }while(!line.empty());
    
        LOG_DEBUG << "Http Head Size is " << headsize;
        std::string res(m_buffer.peek(), headsize);
        LOG_DEBUG << "Http Response :\n" << res;
        m_buffer.retrieve(headsize);
    
        m_haveHandleHead = true;
    
    }
    
    void HttpRequest::uploadFile(const std::string& file, const std::string& contentEnd)
    {
    
        FILE* fp = fopen(file.c_str(), "rb");
        if(fp == NULL)
        {
            LOG_SYSFATAL << "fopen() File :" << file.c_str() << " Errno";
        }
    
        bool isEnd = false;
        ssize_t writtenBytes = 0;
    
        assert(m_buffer.writableBytes() == Buffer::kInitialSize);
    
        while(!isEnd)
        {
            ssize_t nread = fread(m_buffer.beginWrite(), 1, kBufferSize, fp);
            m_buffer.hasWritten(nread);
            while(m_buffer.writableBytes() > 0)
            {
                LOG_TRACE << "file read(): nread: " << nread << " remain: " << m_buffer.writableBytes();
                size_t n = fread(m_buffer.beginWrite(), 1, m_buffer.writableBytes(), fp);
                m_buffer.hasWritten(n);
                if(0 == n)
                {   int err = ferror(fp);
                    if(err)
                    {
                        fprintf(stderr, "fread failed : %s\n", strerror(err));
                    }
                    LOG_DEBUG << "sockets::read finish";
                    isEnd = true;
                    break;
                }
            }
    
            ssize_t nwrite = sockets::write(m_sockfd, m_buffer.peek(), m_buffer.readableBytes());
            if(nwrite < 0) LOG_SYSFATAL << "sockets::write";
            writtenBytes += nwrite;
            LOG_TRACE << "sockets::write nread " << m_buffer.readableBytes() << " nwrite " << nwrite << " writtenBytes " << writtenBytes;
            m_buffer.retrieve(nwrite);
        }
    
        fclose(fp);
    
        m_buffer.retrieveAll();
    
        ssize_t n = sockets::write(m_sockfd, contentEnd.c_str(), contentEnd.size());
        if(n < 0) LOG_SYSFATAL << "sockets::write";
    }
    
    void HttpRequest::downloadFile(const std::string& file)
    {
        assert(m_haveHandleHead);
    
        bool isEnd = false;
        ssize_t nread = 0;
        ssize_t writtenBytes = 0;
        bool haveHandleHead = false;
        bool isDownFile = false;
    
        std::ofstream output(file, std::ios::binary);
        if (!output.is_open()){ // 检查文件是否成功打开
            LOG_SYSFATAL << "open file error" << file;
        }
    
        output.write(m_buffer.peek(), m_buffer.readableBytes());
        writtenBytes += m_buffer.readableBytes();
        m_buffer.retrieve(m_buffer.readableBytes());
    
        LOG_DEBUG << "Content-Length : " << getResponseProperty("Content-Length");
    
        while(!isEnd)
        {
            nread = sockets::read(m_sockfd, m_buffer.beginWrite(), kBufferSize);
            if(nread < 0) LOG_SYSFATAL << "sockets::read";
            m_buffer.hasWritten(nread);
            LOG_TRACE << "sockets::read(): nread: " << nread << " remain: " << m_buffer.writableBytes() << " writtenBytes: " << writtenBytes;
            size_t remain = kBufferSize - nread;
            while(remain > 0)
            {
                size_t n = sockets::read(m_sockfd, m_buffer.beginWrite(), remain);
                if(n < 0) LOG_SYSFATAL << "sockets::read";
                m_buffer.hasWritten(n);
                if(0 == n)
                {
                    LOG_DEBUG << "sockets::read finish";
                    isEnd = true;
                    break;
                }
                remain = remain - n;
            }
    
            output.write(m_buffer.peek(), m_buffer.readableBytes());
            writtenBytes += m_buffer.readableBytes();
            m_buffer.retrieve(m_buffer.readableBytes());
        }
        LOG_DEBUG << " writtenBytes " << writtenBytes;
    
        output.close();
        sockets::close(m_sockfd);
    }
    
    void HttpRequest::SplitString(const std::string& s, std::vector<std::string>& v, const std::string& c)
    {
      std::string::size_type pos1, pos2;
      pos2 = s.find(c);
      pos1 = 0;
      while(std::string::npos != pos2)
      {
        v.push_back(s.substr(pos1, pos2-pos1));
    
        pos1 = pos2 + c.size();
        pos2 = s.find(c, pos1);
      }
      if(pos1 != s.length())
        v.push_back(s.substr(pos1));
    }
    

    关于源码中的Logger
    Logger部分使用的前面文章的Logger类,可通过编辑器批量更改,注释掉,或复制第一篇异步日志的代码直接使用.

    使用示例
    下载图片

    #include "HttpRequest.h"
    
    int main()
    {
    
      HttpRequest ImageReq("http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg");
      ImageReq.setRequestMethod("GET");
      ImageReq.setRequestProperty("Cache-Control", "no-cache");
      ImageReq.setRequestProperty("Content-Type", "application/octet-stream");
      ImageReq.setRequestProperty("Connection", "close\r\n");
    
      ImageReq.connect();
      ImageReq.send();
      ImageReq.handleRead();
      ImageReq.downloadFile("./test.jpg");
    
      return 0;
    }
    
    2018-10-23 20:24:07.257167 [DEBUG] [HttpRequest.hh:194] [detachHttpUrl] detachHttpUrl() url :http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257411 [DEBUG] [HttpRequest.hh:195] [detachHttpUrl] detachHttpUrl() host :img.zcool.cn
    2018-10-23 20:24:07.257561 [DEBUG] [HttpRequest.hh:196] [detachHttpUrl] detachHttpUrl() uri :community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257661 [DEBUG] [HttpRequest.hh:171] [HttpUrl] URL : http://img.zcool.cn/community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg
    2018-10-23 20:24:07.257734 [DEBUG] [HttpRequest.cpp:178] [setRequestMethod] GET /community/01ddc256eb71586ac7257d209712b7.jpg@1280w_1l_2o_100sh.jpg HTTP/1.1
    
    2018-10-23 20:24:08.192206 [DEBUG] [HttpRequest.cpp:142] [connect] HttpRequest::Connector() gethostbyname Successful
    2018-10-23 20:24:08.232474 [DEBUG] [HttpRequest.cpp:149] [connect] sockfd : 3sockets::connect ret : 0
    2018-10-23 20:24:08.233236 [INFO ] [HttpRequest.cpp:158] HttpRequest::connect() sockfd : 3 Successful
    2018-10-23 20:24:08.233453 [DEBUG] [HttpRequest.cpp:169] [connect] HttpRequest::Connector() end
    2018-10-23 20:24:08.257666 [TRACE] [HttpRequest.cpp:212] [handleRead] sockets::read(): nread: 4096 remain: 0
    2018-10-23 20:24:08.258490 [DEBUG] [HttpRequest.cpp:257] [handleRead] Http Head Size is 798
    2018-10-23 20:24:08.259139 [DEBUG] [HttpRequest.cpp:259] [handleRead] Http Response :
    HTTP/1.1 200 OK
    Server: Tengine
    Content-Type: image/jpeg
    Content-Length: 155984
    Connection: close
    Date: Tue, 23 Oct 2018 10:05:56 GMT
    x-oss-request-id: 5BCEF284EA872628A4BA7B32
    ETag: "3D2EABCF4B0809B38A154C5087E5ECD4"
    Last-Modified: Fri, 18 Mar 2016 03:09:12 GMT
    x-oss-object-type: Normal
    x-oss-hash-crc64ecma: 17069884749112719525
    x-oss-storage-class: Standard
    Expires: Thu, 18 Mar 2021 03:09:12 GMT
    Cache-Control: max-age=86400
    Via: cache10.l2cm9[0,304-0,H], cache35.l2cm9[37,0], cache1.cn683[0,200-0,H], cache1.cn683[2,0]
    Age: 8479
    Ali-Swift-Global-Savetime: 1538030855
    X-Cache: HIT TCP_HIT dirn:6:15817583
    X-Swift-SaveTime: Tue, 23 Oct 2018 10:18:38 GMT
    X-Swift-CacheTime: 85638
    Access-Control-Allow-Origin: *
    Timing-Allow-Origin: *
    EagleId: 71db884115402976359511426e
    
    

    该篇为转载,程序调试验证中,功能完善,待后续跟帖。

    转载自: https://www.cnblogs.com/ailumiyana/p/9839170.html

    展开全文
  • 中国专业CDN行业近年来呈现了迅猛增长的态势。根据国内咨询机构艾瑞的统计,从2006年...iframe id="cproIframe_u944267_2" width="250" height="250" src="http://pos.baidu.com/acom?adn=3&amp;at=231&amp;...
  • 术语 通用术语 ... Byzantine Failure(拜占庭错误):指系统中存在除了消息延迟或不可送达的故障以外的错误,包括消息被篡改、节点不按照协议进行处理等,潜在地会对系统造成针对性的破坏。 CDN:Content Delivery...
  • iOS开发笔记 - 网络篇

    2016-03-20 18:14:26
    对iOS应用开发中应用程序如何通过网络获取资源进行了详细的阐述。
  • 本文主要结合作者的《系统安全前沿》作业,相关论文及绿盟李东宏老师的博客,从产业界和学术界分别详细讲解恶意代码攻击溯源的相关知识。在学术界方面,用类似于综述来介绍攻击追踪溯源的不同方法;...
  • 运用之妙夺造化,存乎一心胜天工。 跨境商贸中签订的合同,怎么确保对方能严格遵守和及时执行? 酒店宣称刚打捞上来的三文鱼,怎么追踪捕捞和运输过程中的时间和卫生? 现代数字世界里,怎么证明你是谁?...
  • Flutter:从入门到实践

    2019-07-02 03:04:31
  • 【CTO讲堂】支付接入开发的陷阱有多深? 发表于2015-10-27 15:04| 3926次阅读| 来源CSDN| 0 条评论| 作者蒲婧 ...“仅需3行代码,无需SDK,一键轻松接入”这轻松实现的背后都用到哪些技术?...
  • 1、供应导入组成 供应商导入主要分为供应商头信息导入、供应商地点信息导入、供应商联系人导入三个部分的信息,其他按实际需求进行添加。 供应商头信息导入:导入供应商的头信息 供应商地点信息导入:导入供应商的...
  • 原视频来自:...第51集:区块链资产有匿名性 区块链资产的第二大特点是匿名性。就是别人无法知道你的区块链资产有多少,以及和谁进行了转账。这种匿名性是分不同程度的。 比特币的匿名性是最基本...
  • Apache Thrift设计概要

    2015-07-24 00:54:21
    Apache Thrift设计概要 1. Overview Apache Thrift 的可赞之处是实现了跨超多语言(Java, C++...简洁的四层接口抽象,每一层都可以独立的扩展增强或替换,是另一个可赞的地方。...最后,二进制的编解码方
  • 23种设计模式全解析

    2014-09-07 10:53:32
    一、设计模式的分类 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 ...结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、...
  • 认识网联

    2020-04-12 14:58:19
    一、成立背景 1.1 第三方支付的发展 2002年在银联成立之初,还没有第三方支付的概念,收单业务依然是各商业银行的市场。在这个方向上,中国银联走了和国外的VISA或者MASTERCARD不一样的道路,一开始并没有将银行卡...
  • 第一节 Web介绍知识

    2018-04-06 19:31:36
    重点:Web的工作流程 浏览器上如何工作的初级扩展:1.Web架构熟悉前后端的划分,可以简单地理解为凡是运行在用户设备上的技术都可以称为前端技术( 比如 HTML / CSS / JS,甚至移动设备的 Obj-C / Swift );...
  • http://www.jianshu.com/p/d75a9a8d13b5?utm_campaign=maleskine&utm_content=note&utm_medium=writer_share&utm_source=weibo&url_type=39&object_type=webpage&pos=1 本文翻译自Medium,原作者为 Pawe...
  • app和硬件交互的几种方式:zigbee,蓝牙,wifi、nfc交互、声波。https://www.jianshu.com/p/aaf963fd6ebe 手机访问硬件的几种模式:https://blog.csdn.net/zoomdy/article/details/51594977 ...
1 2 3 4 5 ... 8
收藏数 147
精华内容 58