Skip to content

Commit

Permalink
Merge pull request #146 from jefft0/feat/RequestBuilder-body-NativeRe…
Browse files Browse the repository at this point in the history
…ader

Feat/RequestBuilder with body NativeReader
  • Loading branch information
jefft0 authored Jan 10, 2023
2 parents 2886338 + 7e81ccc commit 8d54781
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;

import static org.junit.Assert.*;
Expand All @@ -34,6 +36,15 @@ public class requestIPFSTests {
(byte)0x70, (byte)0xf0, (byte)0x2b, (byte)0xeb, (byte)0xe9, (byte)0x77, (byte)0xfe, (byte)0x89,
(byte)0xef, (byte)0x67, (byte)0x51, (byte)0x2f, (byte)0x98, (byte)0x82, (byte)0xd8, (byte)0x60
};
// The boundary for a multipart message is a unique string. See https://en.wikipedia.org/wiki/MIME#Multipart_messages
private static String boundary = "------------------------f33e457ed9f80969";
private byte[] addRequestBody =
("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n" +
"hello" +
"\r\n--" + boundary + "--\r\n").getBytes();
private String addRequestExpectedHash = "QmWfVY9y3xjsixTgbd9AorQxH7VtMpzfx2HaWtsoUYecaX";

@Rule
public Timeout globalTimeout = Timeout.seconds(600);
Expand Down Expand Up @@ -118,4 +129,30 @@ public void testCatFileStream() throws Exception {
);
}
}

@Test
public void testAddWithBytesBody() throws Exception {
ArrayList<JSONObject> response = ipfs.newRequest("add")
.withHeader("Content-Type",
"multipart/form-data; boundary=" + boundary)
.withBody(addRequestBody)
.sendToJSONList();

assertEquals("Added file should have the correct CID",
addRequestExpectedHash,
response.get(0).getString("Hash"));
}

@Test
public void testAddWithStreamBody() throws Exception {
ArrayList<JSONObject> response = ipfs.newRequest("add")
.withHeader("Content-Type",
"multipart/form-data; boundary=" + boundary)
.withBody(new ByteArrayInputStream(addRequestBody))
.sendToJSONList();

assertEquals("Added file should have the correct CID",
addRequestExpectedHash,
response.get(0).getString("Hash"));
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
package ipfs.gomobile.android;

import java.io.InputStream;
import java.util.Arrays;

final class InputStreamToGo implements core.Reader {
final class InputStreamToGo implements core.NativeReader {
private final InputStream inputStream;

InputStreamToGo(InputStream inputStream) {
this.inputStream = inputStream;
}

public long read(byte[] p) throws Exception {
long r = inputStream.read(p);
public byte[] nativeRead(long size) throws Exception {
byte[] b = new byte[(int)size];
while (true) {
int n = inputStream.read(b);
if (n == -1) {
inputStream.close(); // Auto-close inputStream when EOF is reached
// The Swift/Go interface converts this to nil.
return new byte[0];
}
if (n > 0) {
if (n == b.length)
return b;
else
return Arrays.copyOf(b, n);
}

if (r == -1) {
inputStream.close(); // Auto-close inputStream when EOF is reached
throw new Exception("EOF");
// Iterate to read more than zero bytes.
}

return r;
}
}
31 changes: 25 additions & 6 deletions go/bind/core/shell.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ func (req *RequestBuilder) StringOptions(key string, value string) {
req.rb.Option(key, value)
}

func (req *RequestBuilder) Body(body Reader) {
req.rb.Body(body)
func (req *RequestBuilder) Body(body NativeReader) {
req.rb.Body(&ReaderWrapper{body})
}

func (req *RequestBuilder) BodyString(body string) {
Expand All @@ -91,8 +91,8 @@ func (req *RequestBuilder) BodyBytes(body []byte) {
req.rb.BodyBytes(dest)
}

func (req *RequestBuilder) FileBody(name string, body Reader) {
fr := files.NewReaderFile(body)
func (req *RequestBuilder) FileBody(name string, body NativeReader) {
fr := files.NewReaderFile(&ReaderWrapper{body})
slf := files.NewSliceDirectory([]files.DirEntry{files.FileEntry(name, fr)})
req.rb.Body(files.NewMultiFileReader(slf, false))
}
Expand All @@ -101,8 +101,27 @@ func (req *RequestBuilder) Header(name, value string) {
req.rb.Header(name, value)
}

type Reader interface {
io.Reader
type NativeReader interface {
// Read up to size bytes and return a new byte array, or nil for EOF.
// Name this function differently to distinguish from io.Reader.
NativeRead(size int) (b []byte, err error)
}

type ReaderWrapper struct {
reader NativeReader
}

func (r *ReaderWrapper) Read(p []byte) (int, error) {
b, err := r.reader.NativeRead(len(p))
if err != nil {
return 0, err
}
if b == nil {
return 0, io.EOF
}
copy(p, b)

return len(b), nil
}

type ReadCloser struct {
Expand Down
26 changes: 17 additions & 9 deletions ios/Bridge/GomobileIPFS/Sources/InputStreamToGo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,32 @@
import Foundation
import Core

public class InputStreamToGo: NSObject, CoreReaderProtocol {
public class InputStreamToGo: NSObject, CoreNativeReaderProtocol {
private var inputStream: InputStream

init(_ inputStream: InputStream) {
self.inputStream = inputStream
self.inputStream.open()
}

public func read(_ buffer: Data?, n len: UnsafeMutablePointer<Int>?) throws {
var read: Int
public func nativeRead(_ size: Int) throws -> Data {
let bytes = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
while true {
let read = self.inputStream.read(bytes, maxLength: size)

let bytes = UnsafeMutablePointer<UInt8>(OpaquePointer((buffer! as NSData).bytes))
read = self.inputStream.read(bytes, maxLength: buffer!.count)
len?.initialize(to: read)
if read < 0 {
throw self.inputStream.streamError!
}
if read == 0 && self.inputStream.streamStatus == .atEnd {
self.inputStream.close()
// The Swift/Go interface converts this to nil.
return Data(count: 0)
}
if read > 0 {
return Data(bytes: bytes, count: read)
}

if read == 0 && self.inputStream.streamStatus == .atEnd {
self.inputStream.close()
throw NSError(domain: "", code: 0, userInfo: ["NSLocalizedDescription": "EOF"])
// Iterate to read more than zero bytes.
}
}
}
5 changes: 4 additions & 1 deletion ios/Bridge/GomobileIPFS/Sources/RequestBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ public enum RequestOption {
case bytes(Data)
}

/// Enum of the different body types: string and bytes
/// Enum of the different body types: stream, string and bytes
public enum RequestBody {
case stream(InputStream)
case string(String)
case bytes(Data)
}
Expand Down Expand Up @@ -77,6 +78,8 @@ public class RequestBuilder {
/// - seealso: [IPFS API Doc](https://docs.ipfs.io/reference/api/http/)
public func with(body: RequestBody) -> RequestBuilder {
switch body {
case .stream(let stream):
self.requestBuilder.body(InputStreamToGo(stream))
case .bytes(let data):
self.requestBuilder.bodyBytes(data)
case .string(let string):
Expand Down
51 changes: 51 additions & 0 deletions ios/Bridge/GomobileIPFSTests/RequestIPFSTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ class RequestIPFSTests: XCTestCase {
private var expectedFileLength: Int = 2940
private var expectedFileSha256: String =
"SHA256 digest: b7042c3f6efc09d29dee4566dec6e34270f02bebe977fe89ef67512f9882d860"
// The boundary for a multipart message is a unique string. See https://en.wikipedia.org/wiki/MIME#Multipart_messages
private static var boundary : String = "------------------------f33e457ed9f80969"
private var addRequestBody : Data =
("--" + boundary + "\r\n" +
"Content-Disposition: form-data; name=\"file\"\r\n" +
"Content-Type: application/octet-stream\r\n\r\n" +
"hello" +
"\r\n--" + boundary + "--\r\n").data(using: .utf8)!;
private var addRequestExpectedHash: String = "QmWfVY9y3xjsixTgbd9AorQxH7VtMpzfx2HaWtsoUYecaX";

override func setUp() {
do {
Expand Down Expand Up @@ -113,4 +122,46 @@ class RequestIPFSTests: XCTestCase {
"response should have the correct SHA256"
)
}

func testAddWithBytesBody() throws {
guard let response = try ipfs.newRequest("add")
.with(header: "Content-Type",
value: "multipart/form-data; boundary=" + RequestIPFSTests.boundary)
.with(body: RequestBody.bytes(addRequestBody))
.sendToDict() else {
XCTFail("error while casting dict for \"add\"")
return
}
guard let hash = response["Hash"] as? String else {
XCTFail("error while casting value associated to \"Hash\" key")
return
}

XCTAssertEqual(
addRequestExpectedHash,
hash,
"Added file should have the correct CID"
)
}

func testAddWithStreamBody() throws {
guard let response = try ipfs.newRequest("add")
.with(header: "Content-Type",
value: "multipart/form-data; boundary=" + RequestIPFSTests.boundary)
.with(body: RequestBody.stream(InputStream(data: addRequestBody)))
.sendToDict() else {
XCTFail("error while casting dict for \"add\"")
return
}
guard let hash = response["Hash"] as? String else {
XCTFail("error while casting value associated to \"Hash\" key")
return
}

XCTAssertEqual(
addRequestExpectedHash,
hash,
"Added file should have the correct CID"
)
}
}

0 comments on commit 8d54781

Please sign in to comment.