基于NativeScript: Camera takePicture and upload with nativescript-background-http
我正在尝试编写一个类来处理文件上传到s3。
我的上传功能如下:
upload() {
const format = enumsModule.ImageFormat.png
cameraModule.takePicture().then(img => {
let savePath = fsModule.knownFolders.documents().path;
console.log('Save Path', savePath);
let fileName = "img_" + new Date().getTime() + "." + format;
console.log('fileName', fileName);
let filePath = fsModule.path.join(savePath, fileName);
console.log('FilePath', filePath);
if (img.saveToFile(filePath, format)) {
let s3Upload = new S3Upload(format, fileName, filePath, this.progressCallback);
s3Upload.getSignedRequest()
.then((url) => {
alert(url);
})
.catch(error => {
console.log('Error!');
alert(error);
})
}
});
}
progressCallback(e: any) {
console.log(e);
}
我的文件上传器看起来像这样:
import {Config} from "../../../shared/config";
import {SignS3Response} from "./signs3Response";
export class S3Upload {
fileType: String;
fileName: String;
filePath: String;
progressCallback: (e) => void;
constructor(fileType: String, fileName: String, filePath: String, progressCallback: (e) => void) {
this.fileType = fileType;
this.fileName = fileName;
this.filePath = filePath;
this.progressCallback = progressCallback;
}
getSignedRequest() {
var xhr = new XMLHttpRequest();
return new Promise<String>((resolve, reject) => {
let urlString = Config.apiUrl + "profile/signS3?fileName=" + this.fileName + "&fileType=" + this.fileType;
console.log(urlString);
xhr.open("GET", urlString);
xhr.onload = () => {
resolve(xhr.response);
}
xhr.onerror = () => {
reject(xhr.response)
}
xhr.send();
})
.then(() => {
console.log(JSON.parse(xhr.responseText));
let response: SignS3Response = <SignS3Response>JSON.parse(xhr.responseText);
return this.uploadFile(response.signedRequest, response.url);
})
}
uploadFile(signedRequest, url) {
return new Promise<String>((resolve, reject) => {
return new Promise<String>((resolve, reject) => {
var xhr = new XMLHttpRequest();
xhr.open("PUT", signedRequest);
xhr.setRequestHeader('x-amz-acl', 'public-read');
xhr.onload = () => {
console.log("onload, outside", JSON.stringify(xhr));
if (xhr.status === 200) {
console.log('Uploaded!');
}
};
xhr.onprogress = () => {
console.log("Progress!");
}
xhr.onerror = () => {
alert("Could not upload file.");
};
xhr.send(fs.File.fromPath(this.filePath));
});
}
});
}
获取已签名的请求工作正常但是当我将其发送到亚马逊上传时,我得到了回复:
{
"UNSENT": 0,
"OPENED": 1,
"HEADERS_RECEIVED": 2,
"LOADING": 3,
"DONE": 4,
"_responseType": "",
"_listeners": {},
"_readyState": 4,
"_options": {
"url": "https://mmprofilesimages.s3.amazonaws.com/1464833363226.png?AWSAccessKeyId=AKIAJCKL57PAMZB54LAQ&Content-Type=png&Expires=1464833423&Signature=vOCsVChKe%2BDfH9PtXmPLuKtQ2cs%3D&x-amz-acl=public-read",
"method": "PUT",
"headers": {
"x-amz-acl": "public-read"
}
},
"_errorFlag": false,
"_response": {},
"_headers": {
"Date": "Thu, 02 Jun 2016 02:09:22 GMT",
"Server": "AmazonS3",
"x-amz-id-2": "0SQdOho2g0/MRICX61fjEDXWZRn3IgSJRCnV86LO2OydSs87cCt/XWz0pwDqomr3TYzu3G44fcA=",
"Content-Type": "application/xml",
"Transfer-Encoding": "Identity",
"x-amz-request-id": "44733A49B05581DF"
},
"_status": 403
}
我知道这是有效的,因为我在打字稿的反编译博客中进行,唯一真正的区别是,我从表单的部分获取文件,在这里我使用的是fs.File.fromPath()。
我甚至试图让它进入我工作的服务器,在我的S3日志中,我有这一行:a01fa7fe68cb4649bd0d6bc76055584010ef30abc23d1b8968ae1494dfde1dc8 benaychhio [02 / Jun / 2016:01:16:42 +0000] 128.177.172.220 - 2473BE6AA6033D7B REST.PUT.OBJECT 1464828438666.png&#34; PUT /1464828438666.png?AWSAccessKeyId=AKIAJFUTN7F7VLAR2SCQ&Content-Type=png&Expires=1464828498&Signature={Signature is here}&amp; x-amz-acl = public-read HTTP / 1.1&#34; 403 AccessDenied 333 - 4 - &#34; - &#34; &#34; montMatchMobile / 1.0 CFNetwork / 758.3.15 Darwin / 15.5.0&#34; -
有人对此有任何建议吗?我也尝试过nativescript-background-html但是我没有从中获得任何回报。
答案 0 :(得分:1)
尝试以下
var eventsDemo = document.getElementById('eventsDemo');
var eventsDemo = document.querySelector('#eventsDemo');
我改变了一些事情,插件添加了一个很好的本地上传状态通知,正如名称所说它可以在你的应用程序在后台/最小化时上传文件
tns plugin add nativescript-background-http
答案 1 :(得分:1)
分享一些代码。 我无法使POST方法工作,但PUT。 Ben,你必须使用bucgkground插件(我试过你的方法并放一个空文件,文件无法识别)。
停止说话,这是代码:
var _ = require("underscore");
var moment = require("moment");
var CryptoJS = require("crypto-js");
import fs = require("file-system");
var Buffer = require("buffer/").Buffer;
var bghttp = require("nativescript-background-http");
import { Injectable } from "@angular/core";
import { RequestOptionsArgs, Headers } from "@angular/http";
import { BaseService } from "./base.service";
import { AppConfig } from "../../app.config";
import { Company } from "../dtos";
@Injectable()
export class ImageService extends BaseService {
uploadCompanyLogo(company: number, fileExtension: string, localPath: string) {
let fileName = company + "." + fileExtension;
this.putFileUpload(fileName, localPath, AppConfig.S3_COMPANY_LOGOS_PATH + "/" + fileName, fileExtension);
}
uploadCompanyCoverImage(company: number, fileExtension: string, localPath: string) {
let fileName = company + "." + fileExtension;
this.putFileUpload(fileName, localPath, AppConfig.S3_COMPANY_COVER_IMAGES_PATH + "/" + fileName, fileExtension);
}
private putFileUpload(fileName: string, localPath: string, s3Path: string, fileExtension: string) {
let url = "http://s3.amazonaws.com/" + AppConfig.S3_BUCKET + "/" + s3Path;
let mimeType = "image/" + fileExtension;
//let payloadHash = this.getPayloadHash(payload);
let payloadHash = "UNSIGNED-PAYLOAD";
let date = moment().utc();
let options: RequestOptionsArgs = {
method: "PUT",
headers: new Headers({
"Host": "s3.amazonaws.com", // Mandatory
"Content-Type": mimeType, // Mandatory
//"Content-Length": "10000", // Mandatory: This header is required for PUTs
// When you specify the Authorization header, you must specify either the x-amz-date or the Date header
"x-amz-date": date.format("YYYYMMDD[T]HHmmss[Z]"),
"x-amz-content-sha256": payloadHash, // Mandatory: It provides a hash of the request payload.
//"x-amz-acl": "public-read" // Optional: By default, all objects are private: only the owner has full control.
//"Authorization" // Will be added by addAuthorizationHeader
//"Content-MD5" // Recommended: The base64 encoded 128-bit MD5 digest of the message
})
};
// Adding the authorization header
let authorization = this.getAuthorizationHeader(options,
AppConfig.S3_ACCESS_KEY_ID, AppConfig.S3_ACCESS_KEY_SECRET,
AppConfig.S3_REGION, AppConfig.S3_BUCKET, s3Path, date, payloadHash);
options.headers.append("Authorization", authorization);
let session = bghttp.session("image-services");
let request = {
url: url,
method: "PUT",
headers: {
"Host": "s3.amazonaws.com", // Mandatory
"Content-Type": mimeType, // Mandatory
//"Content-Length": "10000", // Mandatory: This header is required for PUTs
// When you specify the Authorization header, you must specify either the x-amz-date or the Date header
"x-amz-date": date.format("YYYYMMDD[T]HHmmss[Z]"),
"x-amz-content-sha256": payloadHash, // Mandatory: It provides a hash of the request payload.
//"x-amz-acl": "public-read" // Optional: By default, all objects are private: only the owner has full control.
//"Authorization" // Will be added by addAuthorizationHeader
//"Content-MD5" // Recommended: The base64 encoded 128-bit MD5 digest of the message
"Authorization": authorization
},
description: "{ 'Uploading': '" + fileName + "' }"
};
var task = session.uploadFile("file://" + localPath, request);
//task.on("progress", (e) => console.log(e));
//task.on("error", (e) => console.log(e));
//task.on("complete", (e) => console.log(e));
}
/**
* http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTForms.html
*/
private postFileUpload(fileName: string, localPath: string, s3Path: string, fileExtension: string) {
let date = moment().utc();
let xhr = new XMLHttpRequest();
// action – The URL that processes the request, which must be set to the URL of the bucket.
// For example, if the name of your bucket is examplebucket, the URL is http://examplebucket.s3.amazonaws.com/.
let url = "http://" + AppConfig.S3_BUCKET + ".s3.amazonaws.com/";
xhr.open("POST", url);
xhr.setRequestHeader("Content-Type", "multipart/form-data");
// The policy required for making authenticated requests using HTTP POST is a UTF-8 and Base64 encoded document
// written in JavaScript Object Notation (JSON) that specifies conditions that the request must meet.
let expiration = moment().add(7, "days").utc();
let credential = this.getCredential(AppConfig.S3_ACCESS_KEY_ID, AppConfig.S3_REGION, date);
let policy = {
// The POST policy always contains the expiration and conditions elements.
"expiration": expiration.format("YYYY-MM-DD[T]HH:mm:ss[Z]"), // e.g: "2007-12-01T12:00:00.000Z"
// Each form field that you specify in a form (except x-amz-signature, file, policy, and field names that have an x-ignore- prefix)
// must appear in the list of conditions.
"conditions": [
{ "bucket": AppConfig.S3_BUCKET },
[ "starts-with", "$Content-Type", "image/" ],
[ "starts-with", "$key", "" ],
//policy is not required
{ "x-amz-algorithm": "AWS4-HMAC-SHA256" },
{ "x-amz-credential": credential },
[ "starts-with", "$x-amz-date", "" ]
//x-amz-signature is not required
//file is not required
]
};
let encodedPolicy = this.getEncodedPolicy(policy);
let signature = this.getSignature(encodedPolicy, date,
AppConfig.S3_ACCESS_KEY_SECRET, AppConfig.S3_REGION);
var formData = new FormData();
formData.append("Content-Type", "image/" + fileExtension);
formData.append("key", s3Path);
formData.append("policy", encodedPolicy);
formData.append("x-amz-algorithm", "AWS4-HMAC-SHA256");
formData.append("x-amz-credential", credential);
formData.append("x-amz-date", date.format("YYYYMMDD[T]HHmmss[Z]"));
formData.append("x-amz-signature", signature);
//console.log("Date: " + date.format("YYYYMMDD[T]HHmmss[Z]"));
//console.log("Credential: " + credential);
//console.log("Policy: " + encodedPolicy);
//console.log("Signature: " + signature);
let file = fs.File.fromPath(localPath);
//let payload = file.readSync(error => console.log(error));
// This fails because file is not recognized as file so added as toString()
formData.append("file", file, fileName); // file or payload
xhr.onload = () => {
console.log("Response Text" + xhr.responseText);
console.log("XHR: ", JSON.stringify(xhr));
if (xhr.status === 200) {
console.log('Uploaded!');
}
};
xhr.onprogress = () => {
console.log("Progress!");
}
xhr.onerror = (error) => {
console.log("Could not upload file: " + error);
};
xhr.send(formData);
}
protected getEncodedPolicy(policy): string {
return new Buffer((typeof policy == "string") ? policy : JSON.stringify(policy)).toString("base64");
}
/**
* From: http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTCommonRequestHeaders.html
* Just the date for this service.
*/
protected getAuthorizationHeader(options: RequestOptionsArgs, s3Key: string, s3Secret: string,
s3Region: string, s3Bucket: string, s3Path: string, date, payloadHash: string): string {
// Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,
let credential = this.getCredential(s3Key, s3Region, date);
// SignedHeaders=host;range;x-amz-date,
// A semicolon-separated list of request headers that you used to compute Signature.
// The list includes header names only, and the header names must be in lowercase.
let signedHeaders = _(options.headers.keys())
.chain()
.map(function(hdr) { return hdr.toLowerCase(); })
.sortBy(function(hdr) { return hdr; }) // It is not required
.value()
.join(";");
// Signature=fe5f80f77d5fa3beca038a248ff027d0445342fe2855ddc963176630326f1024
// The 256-bit signature expressed as 64 lowercase hexadecimal characters.
let strToSign = this.getStrToSign(options, date, s3Region, s3Bucket, s3Path, payloadHash);
let signature = this.getSignature(strToSign, date, s3Secret, s3Region).toLowerCase();
// Authorization: AWS4-HMAC-SHA256 Credential=...,SignedHeaders=...,Signature=...
// There is space between the first two components, AWS4-HMAC-SHA256 and Credential
// The subsequent components, Credential, SignedHeaders, and Signature are separated by a comma.
let authorization = "AWS4-HMAC-SHA256 Credential=" + credential +
",SignedHeaders=" + signedHeaders + ",Signature=" + signature;
return authorization;
}
protected getCredential(s3Key: string, s3Region: string, date) {
// Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,
// Your access key ID and the scope information, which includes the date, region, and service that were used to calculate the signature.
return s3Key + "/" + date.format("YYYYMMDD") + "/" + s3Region + "/s3/aws4_request";
}
/**
* From: http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
* @return a strToSign for this request.
*/
protected getStrToSign(options: RequestOptionsArgs, date, s3Region: string, s3Bucket: string, s3Path: string, payloadHash: string) {
// "AWS4-HMAC-SHA256" + "\n" +
// timeStampISO8601Format + "\n" +
// <Scope> + "\n" +
// Hex(SHA256Hash(<CanonicalRequest>))
let cannonicalRequest = this.getCannonicalRequest(options, s3Bucket, s3Path, payloadHash);
let strToSign = "AWS4-HMAC-SHA256\n";
//timeStampISO8601Format
strToSign += date.format("YYYYMMDD[T]HHmmss[Z]") + "\n";
// Scope binds the resulting signature to a specific date, an AWS region, and a service.
// Thus, your resulting signature will work only in the specific region and for a specific service.
// The signature is valid for seven days after the specified date.
strToSign += date.format("YYYYMMDD") + "/" + s3Region + "/s3/aws4_request" + "\n";
//Hex(SHA256Hash(cannonicalRequest))
//SHA256Hash(): Secure Hash Algorithm (SHA) cryptographic hash function.
strToSign += CryptoJS
.SHA256(cannonicalRequest)
.toString(CryptoJS.enc.Hex);
//console.log("<StrToSign>" + strToSign + "<StrToSignEnds>");
return strToSign;
}
protected getPayloadHash(payload):string {
return CryptoJS
.SHA256(payload)
.toString(CryptoJS.enc.Hex); // Not really necessary
}
/**
* From: http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
* @return the cannonical request for this request.
*/
protected getCannonicalRequest(options: RequestOptionsArgs, s3Bucket: string, s3Path: string, payloadHash: string) {
// <HTTPMethod>\n
// <CanonicalURI>\n
// <CanonicalQueryString>\n
// <CanonicalHeaders>\n
// <SignedHeaders>\n
// <HashedPayload>
let cannonicalRequest = "";
// <HTTPMethod>\n
cannonicalRequest += options.method + "\n";
// <CanonicalURI>\n
// CanonicalURI is the URI-encoded version of the absolute path component of the URI —
// everything starting with the "/" that follows the domain name and up to the end of the string or
// to the question mark character ('?') if you have query string parameters.
if (options.headers.get("Host").lastIndexOf(s3Bucket) != -1) {
cannonicalRequest += "/" + encodeURI(s3Path) + "\n";
} else {
cannonicalRequest += "/" + encodeURI(s3Bucket) + "/" + encodeURI(s3Path) + "\n";
}
// <CanonicalQueryString>\n
cannonicalRequest += "\n"; // There is no query string
// <CanonicalHeaders>\n
// CanonicalHeaders is a list of request headers with their values.
// Individual header name and value pairs are separated by the newline character ("\n").
// Header names must be in lowercase. You must sort the header names alphabetically to construct the string
// The CanonicalHeaders list must include the following:
// - HTTP host header.
// - If the Content-Type header is present in the request, you must add it to the CanonicalHeaders list.
// - The x-amz-content-sha256 header is required for all AWS Signature Version 4 requests. It provides a hash of the request payload.
let headers = _(options.headers.keys())
.chain()
.map(function(v, k) { return v.toLowerCase() + ":" + options.headers.get(v).trim(); })
.sortBy(function(v, k) { return v.split(":")[0]; })
.value()
.join("\n");
cannonicalRequest += headers + "\n";
cannonicalRequest += "\n"; // ?
// <SignedHeaders>\
// SignedHeaders is an alphabetically sorted, semicolon-separated list of lowercase request header names.
let signedHeaders = _(options.headers.keys())
.chain()
.map(function(hdr) { return hdr.toLowerCase(); })
.sortBy(function(hdr) { return hdr; })
.value()
.join(";");
cannonicalRequest += signedHeaders + "\n";
// <HashedPayload>
cannonicalRequest += payloadHash;
//console.log("<CannonicalRequest>" + cannonicalRequest + "<CannonicalRequestEnds>");
return cannonicalRequest;
}
/**
* From: http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
* @return a signature for this request.
*/
protected getSignature(strToSign: string, date, s3Secret, s3Region) {
// HMAC-SHA256(): Computes HMAC by using the SHA256 algorithm with the signing key provided. This is the final signature.
// https://code.google.com/archive/p/crypto-js/
// var hash = CryptoJS.HmacSHA256("Message", "Secret Passphrase");
// WARNING: The way amazon presents the key/phrase is the oposite to the method signature
// DateKey = HMAC-SHA256("AWS4"+"<SecretAccessKey>", "<YYYYMMDD>")
let dateKey = CryptoJS.HmacSHA256(date.format("YYYYMMDD"), "AWS4" + s3Secret);
// DateRegionKey = HMAC-SHA256(<DateKey>, "<aws-region>")
let dateRegionKey = CryptoJS.HmacSHA256(s3Region, dateKey);
// DateRegionServiceKey = HMAC-SHA256(<DateRegionKey>, "<aws-service>")
let dateRegionServiceKey = CryptoJS.HmacSHA256("s3", dateRegionKey);
// SigningKey = HMAC-SHA256(<DateRegionServiceKey>, "aws4_request")
let signKey = CryptoJS.HmacSHA256("aws4_request", dateRegionServiceKey);
// sign the request string
let signature = CryptoJS
.HmacSHA256(strToSign, signKey)
.toString(CryptoJS.enc.Hex);
//console.log("Signature :", signature);
return signature;
}
}
这是对算法的测试(从亚马逊提取)。 文件名中有$的问题(第二种情况),但我并不关心(它不是encodeURIComponent):
var moment = require("moment");
import "reflect-metadata";
import { ImageService } from "../../shared/services/image.service";
import { RequestOptionsArgs, Headers } from "@angular/http";
import { RequestOptions } from "@angular/http";
declare var describe;
declare var it;
declare var expect;
class ImageServiceTest extends ImageService {
public getEncodedPolicy(policy): string {
return super.getEncodedPolicy(policy);
}
public getAuthorizationHeader(options, s3Key: string, s3Secret: string, s3Region: string, s3Bucket: string, s3Path: string, date, payload): string {
return super.getAuthorizationHeader(options, s3Key, s3Secret, s3Region, s3Bucket, s3Path, date, payload);
}
public getCannonicalRequest(options, s3Bucket: string, s3Path: string, payload) {
return super.getCannonicalRequest(options, s3Bucket, s3Path, payload);
}
public getPayloadHash(payload):string {
return super.getPayloadHash(payload);
}
public getStrToSign(options, date, s3Region: string, s3Bucket: string, s3Path: string, payload) {
return super.getStrToSign(options, date, s3Region, s3Bucket, s3Path, payload);
}
public getSignature(strToSign: string, date, s3Secret, s3Region) {
return super.getSignature(strToSign, date, s3Secret, s3Region);
}
}
describe("GET S3 AWS Test:", function() {
let imageService = new ImageServiceTest();
let date = moment("20130524", "YYYYMMDD");
// Taken from: http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html
let s3Region = "us-east-1";
let s3Bucket = "examplebucket";
let s3Path = "test.txt";
let payload = "";
let s3Key = "AKIAIOSFODNN7EXAMPLE";
let s3Secret = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
let payloadHash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
let options: RequestOptionsArgs = {
method: "GET",
headers: new Headers({
"Host": "examplebucket.s3.amazonaws.com",
"Range": "bytes=0-9",
"x-amz-content-sha256": payloadHash,
"x-amz-date": date.format("YYYYMMDD[T]HHmmss[Z]")
})
};
let expectedCannonicalRequest =
"GET\n" +
"/test.txt\n" +
"\n" + // No query parameters
"host:examplebucket.s3.amazonaws.com\n" +
"range:bytes=0-9\n" +
"x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\n" +
"x-amz-date:20130524T000000Z\n" +
"\n" + // ??
"host;range;x-amz-content-sha256;x-amz-date\n" +
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
let expectedStringToSign =
"AWS4-HMAC-SHA256\n" +
"20130524T000000Z\n" +
"20130524/us-east-1/s3/aws4_request\n" +
"7344ae5b7ee6c3e7e6b0fe0640412a37625d1fbfff95c48bbb2dc43964946972";
let expectedSignature = "f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41";
let expectedAuthorizationHeader = "AWS4-HMAC-SHA256 Credential=AKIAIOSFODNN7EXAMPLE/20130524/us-east-1/s3/aws4_request,SignedHeaders=host;range;x-amz-content-sha256;x-amz-date,Signature=f0e8bdb87c964420e857bd35b5d6ed310bd44f0170aba48dd91039c6036bdb41";
it ("Check the GET PayloadHash.", function() {
expect(imageService.getPayloadHash(payload)).toEqual(payloadHash);
});
it ("Check the GET CanonicalRequest.", function() {
expect(imageService.getCannonicalRequest(options, s3Bucket, s3Path, payloadHash)).toEqual(expectedCannonicalRequest);
});
it ("Check the GET StringToSign.", function() {
expect(imageService.getStrToSign(options, date, s3Region, s3Bucket, s3Path, payloadHash)).toEqual(expectedStringToSign);
});
it ("Check the GET Signature.", function() {
expect(imageService.getSignature(expectedStringToSign, date, s3Secret, s3Region)).toEqual(expectedSignature);
});
it ("Check the GET Authorization Header.", function() {
expect(imageService.getAuthorizationHeader(options, s3Key, s3Secret, s3Region, s3Bucket, s3Path, date, payloadHash)).toEqual(expectedAuthorizationHeader);
});
});