在 Objective-c 中使用 GTMHTTPFetcher POST 将 GPX 文件上传到 Strava API

Uploading GPX file to Strava API with GTMHTTPFetcher POST in Objective-c

在探索了 Strava API (http://strava.github.io/api/v3) 与 GTMHTTPFetcher 的使用 2 天后,我现在完全无法将 .gpx 文件上传到我的帐户,所以我决定询问在这里寻找答案。我成功接收并存储了一个具有必要 'view_private,write' 权限的访问令牌,服务器正在响应请求,我设法接收和更改存储在我帐户中的数据。尝试上传 .gpx (xml) 文件时出现错误(错误请求):

The operation couldn’t be completed. (com.google.HTTPStatus error 400.)

对于构建 HTTP POST 请求,我显然有些不明白,我尝试了不同的方法来试验 http header 中的值,更改了获取 url在“/上传?”之后使用 file=... 等无济于事。对于身份验证,我使用 GTMOAuth2Authentication.

我的代码:

- (void) uploadToStrava {

    NSString *filePath = @"somefilepath.gpx";
    NSURL *url = [NSURL URLWithString:@"https://www.strava.com/api/v3/uploads?data_type=gpx"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    [request setHTTPMethod:@"POST"];
    [request setHTTPBody:[NSData dataWithContentsOfFile:filePath]];

    [self.signedStravaAuth authorizeRequest:request];

    GTMHTTPFetcher* myStravaFetcher = [GTMHTTPFetcher fetcherWithRequest:request];
    [myStravaFetcher beginFetchWithDelegate:self
                          didFinishSelector:@selector(myStravaFetcher:finishedWithData:error:)];
    self.currentFetcher = myStravaFetcher;

}

- (void) myStravaFetcher:(GTMHTTPFetcher *)fetcher finishedWithData:(NSData *)data error:(NSError *) error {
    if (error != nil) {
        [self handleErrorWithText:nil];
        NSLog(@"error %@", [error localizedDescription]);
    } else {
        NSString *info = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
        NSDictionary *result = [self.jsonParser objectWithString:info];
        NSLog(@"Strava response: %@", result);
    }
}

API 文档在 CURL 中附带以下示例请求:

$ curl -X POST https://www.strava.com/api/v3/uploads \
    -H "Authorization: Bearer 83ebeabdec09f6670863766f792ead24d61fe3f9" \
    -F activity_type=ride \
    -F file=@test.fit \
    -F data_type=fit

相关问题:Using AFNetworking to POST a file asynchronously with upload progress to Strava V3 API

如果您收到 HTTP 400,则表示 Strava V3 API 拒绝了您的请求 - 通常来自上传端点的 400 包含有关错误信息的响应信息。我将从检查错误响应的响应主体开始。我不熟悉 GTMHTTPFetcher,但您应该能够调试和观察响应主体,或者您可以使用 Charles Proxy 之类的工具来调试请求和响应。尝试比较 curl 请求和来自您的 Objective-C 代码的请求。

Strava V3 上传 API 期望上传的文件部分在 file 参数中 - 同样,在不熟悉 GTMHTTPFetcher 的情况下,我不能确定它在做什么,但您似乎没有指定文件数据是名为 file.

multipart/form-data 部分的一部分

远景:你似乎也在混合 POST 正文和查询字符串参数,所以你的 data_type 参数 可能 没有它通过。我会保持一致并将所有参数放在 POST 正文中。

所以我自己格式化了正文,并且在下面的示例的帮助下了解了如何完成此操作 link:http://nthn.me/posts/2012/objc-multipart-forms.html. I experienced troubles with the last boundary though because it needed an extra -- afterwards which was missing in the example. Luckily I found out about this quickly because I was reading up about the multipart HTTP POST protocol here: http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

    NSURL *url = [NSURL URLWithString:@"https://www.strava.com/api/v3/uploads"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setHTTPMethod:@"POST"];

    NSString *boundary = @"YOUR_BOUNDARY_STRING";
    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
    [request addValue:contentType forHTTPHeaderField:@"Content-Type"];

    NSMutableData *body = [NSMutableData data];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\"; filename=\"%@\"\r\n", usefullFileName] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[@"Content-Type: application/octet-stream\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[NSData dataWithContentsOfFile:filePath]];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"data_type\"\r\n\r\n%@", @"gpx"] dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"name\"\r\n\r\n%@", @"Some Name"] dataUsingEncoding:NSUTF8StringEncoding]];

    [body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];

    [request setHTTPBody:body];
    NSString *strData = [[NSString alloc]initWithData:body encoding:NSUTF8StringEncoding];
    NSLog(@"%@", strData); // human readable body in log

    [self.signedStravaAuth authorizeRequest:request];

    GTMHTTPFetcher* myStravaFetcher = [GTMHTTPFetcher fetcherWithRequest:request];
    [myStravaFetcher beginFetchWithDelegate:self
                         didFinishSelector:@selector(myStravaFetcher:finishedWithData:error:)];

现在服务器响应 "Your activity is still being processed.",我看到地图已到达我在 Strava 上的帐户。

我确信 GTMHTTPFetcher 能够提供更容易 read/handle 的解决方案,但我还没有找到它,这个解决方案令人满意。