Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

fixing multipart

  • Loading branch information...
commit d214274ec53f8802eac3bf38435fbcd372abafb2 1 parent ac85d08
@gabriel gabriel authored
View
22 src/org/httpclient/HttpClient.as
@@ -8,6 +8,7 @@ package org.httpclient {
import flash.events.EventDispatcher;
import flash.events.Event;
+ import flash.errors.IllegalOperationError;
import org.httpclient.http.Put;
import org.httpclient.http.Post;
@@ -115,10 +116,8 @@ package org.httpclient {
else if (method == "POST") httpRequest = new Post();
else throw new ArgumentError("Method must be PUT or POST");
- // Comment out to compile under flash
- //httpRequest.multipart = new Multipart([ new FilePart(file) ]);
- throw new DefinitionError("Upload not supported; Rebuild with upload support.");
-
+ //httpRequest.setMultipart(new Multipart([ new FilePart(file) ]));
+ throw new IllegalOperationError("Not supported, comment out the line above");
request(uri, httpRequest);
}
@@ -148,7 +147,20 @@ package org.httpclient {
}
/**
- * Post with data.
+ * Post with multipart.
+ *
+ * @param uri
+ * @param multipart
+ */
+ public function postMultipart(uri:URI, multipart:Multipart):void {
+ var post:Post = new Post();
+ post.setMultipart(multipart);
+ request(uri, post);
+ }
+
+ /**
+ * Post with raw data.
+ *
* @param uri
* @param body
* @param contentType
View
24 src/org/httpclient/HttpRequest.as
@@ -7,6 +7,7 @@ package org.httpclient {
import com.adobe.net.URI;
import com.adobe.net.URIEncodingBitmap;
import com.hurlant.util.Base64;
+ import flash.errors.IllegalOperationError;
import flash.utils.ByteArray;
import org.httpclient.http.multipart.Multipart;
@@ -28,16 +29,18 @@ package org.httpclient {
/**
* Create request.
*
- * The request body can be anything but should respond to:
+ * The raw request body can be anything but should respond to:
* - readBytes(bytes:ByteArray, offset:uint, length:uint)
* - length
* - bytesAvailable
* - close
*
+ * To set multipart form data, don't pass in a body. Use request.setMultipart(...)
+ * To set form data, don't pass in a body. Use request.setFormData(...)
+ *
* @param method
* @param header
* @param body
- *
*/
public function HttpRequest(method:String, header:HttpHeader = null, body:* = null) {
_method = method;
@@ -91,6 +94,8 @@ package org.httpclient {
* - length
* - bytesAvailable
* - close
+ *
+ * @throws IllegalOperationError if content is already set
*/
public function set body(body:*):void {
_body = body;
@@ -106,16 +111,20 @@ package org.httpclient {
}
/**
- * Set form data.
+ * Set request body as form data content (type is x-www-form-urlencoded)
+ *
* @param params Array of key value pairs: [ { name: "Foo", value: "Bar" }, { name: "Baz", value: "Bewm" },... ]
* @param sep Separator
+ * @throws IllegalOperationError if content is already set
*/
public function setFormData(params:Array, sep:String = "&"):void {
+ if (_body) throw new IllegalOperationError("The body content was already set; You can only set using either setMultipart, setFormData or body=");
+
_header.replace("Content-Type", "application/x-www-form-urlencoded");
_body = new ByteArray();
_body.writeUTFBytes(params.map(function(item:*, index:int, array:Array):String {
- return encodeURI(item["name"]) + "=" + encodeURI(item["value"]);
+ return encodeURI(item.name) + "=" + encodeURI(item.value);
}).join(sep));
_body.position = 0;
@@ -124,9 +133,12 @@ package org.httpclient {
}
/**
- * Set multipart.
+ * Set request body as multipart content.
+ * @throws IllegalOperationError if content is already set
*/
- public function set multipart(multipart:Multipart):void {
+ public function setMultipart(multipart:Multipart):void {
+ if (_body) throw new IllegalOperationError("The body content was already set; You can only set using either setMultipart, setFormData or body=");
+
_header.replace("Content-Type", "multipart/form-data; boundary=" + Multipart.BOUNDARY);
_header.replace("Content-Length", String(multipart.length));
_body = multipart;
View
37 src/org/httpclient/http/multipart/EndPart.as
@@ -1,37 +0,0 @@
-/**
- * Copyright (c) 2007 Gabriel Handford
- * See LICENSE.txt for full license information.
- */
-package org.httpclient.http.multipart {
-
- import flash.utils.ByteArray;
-
- public class EndPart extends Part {
-
- /**
- * Create part section.
- *
- * @param none
- */
- public function EndPart() {
- super(new ByteArray());
- }
-
- /**
- * Build header.
- * @return Header as byte array
- */
- override protected function header():ByteArray {
- var bytes:ByteArray = new ByteArray();
-
- // Boundary
- bytes.writeUTFBytes("--" + Multipart.BOUNDARY + "--\r\n");
- bytes.position = 0;
- return bytes;
- }
-
- override protected function footer():ByteArray {
- return new ByteArray();
- }
- }
-}
View
2  src/org/httpclient/http/multipart/FilePart.as
@@ -15,7 +15,7 @@ package org.httpclient.http.multipart {
* @param contentType Content type
*/
public function FilePart(file:File, contentType:String = "application/octet-stream") {
- super(HttpFileStream.readFile(file), contentType, [ { name: "filename", value: file.name } ]);
+ super("file", HttpFileStream.readFile(file), contentType, "binary");
}
}
View
1  src/org/httpclient/http/multipart/Multipart.as
@@ -21,7 +21,6 @@ package org.httpclient.http.multipart {
*/
public function Multipart(parts:Array) {
_parts = parts;
- _parts.push(new EndPart());
}
/**
View
59 src/org/httpclient/http/multipart/Part.as
@@ -8,9 +8,9 @@ package org.httpclient.http.multipart {
public class Part {
- private var _contentType:String;
- private var _params:Array;
- private var _boundary:String;
+ private var _name:String;
+ private var _contentType:String;
+ private var _contentTransferEncoding:String;
private var _header:ByteArray;
private var _payload:*;
@@ -21,14 +21,23 @@ package org.httpclient.http.multipart {
*
* @param payload
* @param contentType
- * @param params [ { name: "Name", value: "Value" }, ... ]
- * @param boundary Boundary or null, and a random one is generated
+ * @param contentTransferEncoding
+ *
*/
- public function Part(payload:*, contentType:String = "application/octet-stream", params:Array = null) {
+ public function Part(name:String, payload:*, contentType:String = null, contentTransferEncoding:String = null) {
+
+ // Convert payload to UTF bytes if its a String
+ if (payload is String) {
+ var stringBytes:ByteArray = new ByteArray();
+ stringBytes.writeUTFBytes(payload);
+ stringBytes.position = 0;
+ payload = stringBytes;
+ }
+
+ _name = name;
_payload = payload;
_contentType = contentType;
- _params = params;
- if (!_params) _params = [];
+ _contentTransferEncoding = contentTransferEncoding;
_header = header();
_footer = footer();
@@ -79,12 +88,25 @@ package org.httpclient.http.multipart {
* Close payload.
*/
public function close():void {
- if (!(_payload is ByteArray))
- _payload.close();
+ if (!(_payload is ByteArray)) _payload.close(); // TODO: Can we check responds to?
}
/**
* Build header.
+ *
+ * Example,
+ * --BOUNDARY
+ * Content-Disposition: form-data; name="field1"
+ *
+ * <payload>
+ *
+ * Or,
+ * --BOUNDARY
+ * Content-Disposition: form-data; name="userfile"; filename="$filename"
+ * Content-Type: $mimetype
+ * Content-Transfer-Encoding: binary
+ *
+ * <payload>
* @return Header as byte array
*/
protected function header():ByteArray {
@@ -95,14 +117,14 @@ package org.httpclient.http.multipart {
// Content disposition
bytes.writeUTFBytes("Content-Disposition: form-data; ");
-
- // Params
- bytes.writeUTFBytes(_params.map(function(item:*, index:int, array:Array):String { return item["name"] + "=\"" + item["value"] + "\""; }).join("; "));
+ if (_name) bytes.writeUTFBytes("name=\"" + _name + "\"")
bytes.writeUTFBytes("\r\n");
// Content type
- bytes.writeUTFBytes("Content-Type: " + _contentType + "\r\n");
- //bytes.writeUTFBytes("Content-Length: " + _payload.length + "\r\n");
+ if (_contentType) bytes.writeUTFBytes("Content-Type: " + _contentType + "\r\n");
+
+ // Content transfer encoding
+ if (_contentTransferEncoding) bytes.writeUTFBytes("Content-Transfer-Encoding: " + _contentTransferEncoding + "\r\n");
// Empty line
bytes.writeUTFBytes("\r\n");
@@ -111,9 +133,14 @@ package org.httpclient.http.multipart {
return bytes;
}
+ /**
+ * Build footer
+ * Example,
+ * --BOUNDARY--
+ */
protected function footer():ByteArray {
var bytes:ByteArray = new ByteArray();
- bytes.writeUTFBytes("\r\n");
+ bytes.writeUTFBytes("--" + Multipart.BOUNDARY + "--\r\n");
bytes.position = 0;
return bytes;
}
View
19 src/org/httpclient/http/multipart/StringPart.as
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2007 Gabriel Handford
+ * See LICENSE.txt for full license information.
+ */
+package org.httpclient.http.multipart {
+
+ public class StringPart extends Part {
+
+ /**
+ * Create string name, value part.
+ * @param name
+ * @param value
+ */
+ public function StringPart(name:String, value:String) {
+ super(name, value);
+ }
+
+ }
+}
View
2  test/Test.mxml
@@ -36,7 +36,7 @@
//ts.addTest(new DeliciousTests());
} else {
/* For single tests */
- ts.addTest(new UriEscapeTest("testEscapePath"));
+ ts.addTest(new S3PostTest("testPost"));
//ts.addTest(new TagsTest("testGetTags"));
}
View
4 test/httpclient/http/UriEscapeTest.as
@@ -46,11 +46,11 @@ package httpclient.http {
var request:HttpRequest = new HttpRequest("GET");
// /bar?action=getevent&token=KD/+c=|sau=|ted=
- var uri:URI = new URI("http://foo.com/bar?action=getevent&token=KD%2F%2Bc%3D%7Csau%3D%7Cted%3D");
+ var uri:URI = new URI("http://foo.com/bar?token=KD%2F%2Bc%3D%7Csau%3D%7Cted%3D");
var bytes:ByteArray = request.getHeader(uri);
var s:String = bytes.readUTFBytes(bytes.length);
// TODO: URI doesn't maintain order this test is even more brittle than before :O
- assertEquals("GET /bar?action=getevent&token=KD%2F%2Bc%3D%7Csau%3D%7Cted%3D HTTP/1.1\r\nHost: foo.com\r\nConnection: close\r\n\r\n", s)
+ assertEquals("GET /bar?token=KD%2F%2Bc%3D%7Csau%3D%7Cted%3D HTTP/1.1\r\nHost: foo.com\r\nConnection: close\r\n\r\n", s)
}
}
View
81 test/s3/S3PostOptions.as
@@ -0,0 +1,81 @@
+package s3 {
+
+ import com.adobe.serialization.json.JSON;
+ import com.hurlant.util.Base64;
+ import com.hurlant.crypto.hash.SHA1;
+ import com.hurlant.crypto.hash.HMAC;
+ import flash.utils.ByteArray;
+ import flash.net.URLVariables;
+
+ public class S3PostOptions {
+
+ private var bucketName:String;
+ private var objectName:String;
+ private var accessKey:String;
+ private var contentType:String;
+ private var acl:String;
+ private var isFlash:Boolean;
+
+ private static var hmac:HMAC;
+
+ public function S3PostOptions(bucketName:String, objectName:String, accessKey:String, options:Object = null) {
+ this.bucketName = bucketName;
+ this.objectName = objectName;
+ this.accessKey = accessKey;
+ this.contentType = options.contentType;
+ this.acl = options.acl;
+ this.isFlash = options.isFlash;
+
+ if (!hmac) hmac = new HMAC(new SHA1());
+ }
+
+ public function getURLVariables(secretAccessKey:String):URLVariables {
+ var postVariables:URLVariables = new URLVariables();
+ postVariables.key = objectName;
+ postVariables.AWSAccessKeyId = accessKey;
+ if (contentType) postVariables["Content-Type"] = contentType;
+ if (acl) postVariables.acl = acl;
+ postVariables.policy = getPolicy();
+ postVariables.signature = getSignature(secretAccessKey, postVariables.policy);
+ return postVariables;
+ }
+
+ public function getExpiration():String {
+ var year:Number = new Date().getFullYear() + 1;
+ return year + "-01-01T12:00:00.000Z"
+ }
+
+ /**
+ * Get policy, in JSON format encoded as Base64.
+ * @param encode If true, will return Base64 encoded.
+ * @return Policy string
+ */
+ public function getPolicy(encode:Boolean = true):String {
+ var policyOptions:Object = {};
+ policyOptions.expiration = getExpiration();
+ var conditions:Array = [];
+ conditions.push({ bucket: bucketName });
+ conditions.push({ key: objectName });
+ conditions.push({ "Content-Type":contentType });
+
+ if (isFlash) conditions.push([ "starts-with", "$Filename", "" ]);
+
+ policyOptions.conditions = conditions;
+ var policy:String = JSON.encode(policyOptions);
+ if (encode) policy = Base64.encode(policy);
+ return policy;
+ }
+
+ public function getSignature(secretAccessKey:String, policyEncoded:String):String {
+ var policyEncodedBytes:ByteArray = new ByteArray();
+ policyEncodedBytes.writeUTFBytes(policyEncoded);
+
+ var secretAccessKeyBytes:ByteArray = new ByteArray();
+ secretAccessKeyBytes.writeUTFBytes(secretAccessKey);
+
+ var signatureBytes:ByteArray = hmac.compute(secretAccessKeyBytes, policyEncodedBytes);
+ return Base64.encodeByteArray(signatureBytes);
+ }
+ }
+
+}
View
84 test/s3/S3PostTest.as
@@ -0,0 +1,84 @@
+package s3 {
+
+ import flexunit.framework.TestCase;
+ import flexunit.framework.TestSuite;
+
+ import org.httpclient.*;
+ import org.httpclient.http.*;
+ import org.httpclient.events.*;
+ import org.httpclient.http.multipart.*;
+
+ import com.adobe.net.*;
+
+ import flash.utils.ByteArray;
+ import flash.events.Event;
+ import flash.events.ErrorEvent;
+
+ public class S3PostTest extends TestCase {
+
+ public function S3PostTest(methodName:String):void {
+ super(methodName);
+ }
+
+ public static function suite():TestSuite {
+ var ts:TestSuite = new TestSuite();
+ ts.addTest(new S3PostTest("testPost"));
+ return ts;
+ }
+
+ /**
+ * Test post with multipart form data.
+ */
+ public function testPost():void {
+ var client:HttpClient = new HttpClient();
+
+ var bucketName:String = "http-test-put";
+ var objectName:String = "test-post.txt";
+
+ var uri:URI = new URI("http://" + bucketName + ".s3.amazonaws.com/" + objectName);
+ var contentType:String = "text/plain";
+
+ var accessKey:String = "0RXZ3R7Y034PA8VGNWR2";
+ var postOptions:S3PostOptions = new S3PostOptions(bucketName, objectName, accessKey, { contentType: contentType });
+ var policy:String = postOptions.getPolicy();
+
+ // This is how I got the signature below
+ // var secretAccessKey:String = "<SECRET KEY>";
+ // var signature:String = postOptions.getSignature(secretAccessKey, policy);
+ var signature:String = "pLlELBq/ky4o7X5arS5BHRjcPnQ=";
+
+ var data:ByteArray = new ByteArray();
+ data.writeUTFBytes("This is a test");
+ data.position = 0;
+
+ var multipart:Multipart = new Multipart([
+ new Part("key", objectName),
+ new Part("Content-Type", contentType),
+ new Part("AWSAccessKeyId", accessKey),
+ new Part("Policy", policy),
+ new Part("Signature", signature),
+ new Part("file", data, contentType, "binary")
+ ]);
+
+ var response:HttpResponse = null;
+
+ client.listener.onComplete = addAsync(function():void {
+ assertNotNull(response);
+ }, 20 * 1000);
+
+ client.listener.onStatus = function(event:HttpStatusEvent):void {
+ response = event.response;
+ Log.debug("Response: " + response);
+ assertTrue(response.isSuccess);
+ };
+
+ client.listener.onError = function(event:ErrorEvent):void {
+ fail(event.text);
+ };
+
+ client.postMultipart(uri, multipart);
+ }
+
+ }
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.