Skip to content

Commit

Permalink
feat: add upload
Browse files Browse the repository at this point in the history
  • Loading branch information
jiacai2050 committed May 5, 2024
1 parent bdc186f commit cef1dc2
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 22 deletions.
29 changes: 8 additions & 21 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,27 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, macos-latest]
zig-version: [master, 0.12.0]
steps:
- uses: actions/checkout@v4
- uses: goto-bus-stop/setup-zig@v2
with:
version: ${{ matrix.zig-version }}
- name: Install deps
run: |
sudo apt update && sudo apt install -y valgrind
- name: Run tests
run: |
make test
- name: Run examples
run: |
make serve &
sleep 15
make run
- name: Install deps
if: matrix.os == 'ubuntu-latest'
run: |
sudo apt update && sudo apt install -y valgrind
- name: Memory leak detect
if: matrix.os == 'ubuntu-latest'
run: |
zig build -Dcpu=baseline --verbose
Expand All @@ -47,20 +51,3 @@ jobs:
valgrind --leak-check=full --tool=memcheck \
--show-leak-kinds=all --error-exitcode=1 ${bin}
done
test-macos:
timeout-minutes: 10
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest]
zig-version: [master, 0.12.0]
steps:
- uses: actions/checkout@v4
- uses: goto-bus-stop/setup-zig@v1
with:
version: ${{ matrix.zig-version }}
- name: Run examples
run: |
make run
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ prepare:
clean:
rm -rf zig-cache zig-out

serve:
cd server && go run main.go

run:
zig build run-basic -freference-trace
zig build run-advanced -freference-trace
Expand Down
28 changes: 28 additions & 0 deletions examples/basic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ const Allocator = mem.Allocator;
const curl = @import("curl");
const Easy = curl.Easy;

const LOCAL_SERVER_ADDR = "http://localhost:8182";

fn get(allocator: Allocator, easy: Easy) !void {
try easy.setVerbose(true);
const resp = try easy.get("https://httpbin.org/anything");
Expand Down Expand Up @@ -66,6 +68,27 @@ fn post(allocator: Allocator, easy: Easy) !void {
});
}

fn upload(allocator: Allocator, easy: Easy) !void {
const path = "LICENSE";
const resp = try easy.upload(LOCAL_SERVER_ADDR ++ "/anything", path);
const Response = struct {
method: []const u8,
body_len: usize,
};
const parsed = try std.json.parseFromSlice(Response, allocator, resp.body.?.items, .{ .ignore_unknown_fields = true });
defer parsed.deinit();

try std.testing.expectEqualDeep(parsed.value, Response{
.body_len = 1086,
.method = "PUT",
});

std.debug.print("Status code: {d}\nBody: {s}\n", .{
resp.status_code,
resp.body.?.items,
});
}

pub fn main() !void {
const allocator = std.heap.page_allocator;

Expand All @@ -80,5 +103,10 @@ pub fn main() !void {
try get(allocator, easy);

println("POST demo");
easy.reset();
try post(allocator, easy);

println("Upload demo");
easy.reset();
try upload(allocator, easy);
}
3 changes: 3 additions & 0 deletions server/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module server

go 1.20.0
54 changes: 54 additions & 0 deletions server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

import (
"encoding/json"
"fmt"
"html"
"io"
"log"
"net/http"
)

type Response struct {
Method string `json:"method"`
Path string `json:"path"`
Body string `json:"body"`
BodyLen int `json:"body_len"`
Headers map[string]string `json:"headers"`
}

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

http.HandleFunc("/anything", func(w http.ResponseWriter, r *http.Request) {
bs, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer r.Body.Close()

headers := map[string]string{}
for k, v := range r.Header {
headers[k] = v[0]
}

ret := Response{
Method: r.Method,
Path: r.URL.Path,
Body: string(bs),
BodyLen: len(bs),
Headers: headers,
}
err = json.NewEncoder(w).Encode(ret)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})

log.Println("Listening on :8182")
log.Fatal(http.ListenAndServe(":8182", nil))
}
50 changes: 49 additions & 1 deletion src/Easy.zig
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,33 @@ pub const MultiPart = struct {
}
};

pub const Upload = struct {
file: std.fs.File,
file_len: u64,

pub fn init(path: []const u8) !Upload {
const file = try std.fs.cwd().openFile(path, .{});
const md = try file.metadata();
return .{ .file = file, .file_len = md.size() };
}

pub fn deinit(self: Upload) void {
self.file.close();
}

pub fn readFunction(ptr: [*c]c_char, size: c_uint, nmemb: c_uint, user_data: *anyopaque) callconv(.C) c_uint {
const up: *Upload = @alignCast(@ptrCast(user_data));
const max_length = @min(size * nmemb, up.file_len);
var buf: [*]u8 = @ptrCast(ptr);
const n = up.file.read(buf[0..max_length]) catch |e| {
std.log.err("Upload read file failed, err:{any}\n", .{e});
return c.CURL_READFUNC_ABORT;
};

return @intCast(n);
}
};

/// Init options for Easy handle
pub const Options = struct {
// Note that the vendored libcurl is compiled with mbedtls and does not include a CA bundle,
Expand Down Expand Up @@ -201,6 +228,12 @@ pub fn setMultiPart(self: Self, multi_part: MultiPart) !void {
try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_MIMEPOST, multi_part.mime_handle));
}

pub fn setUpload(self: Self, up: *Upload) !void {
try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_UPLOAD, @as(c_int, 1)));
try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_READFUNCTION, Upload.readFunction));
try checkCode(c.curl_easy_setopt(self.handle, c.CURLOPT_READDATA, up));
}

pub fn reset(self: Self) void {
c.curl_easy_reset(self.handle);
}
Expand Down Expand Up @@ -257,7 +290,7 @@ pub fn head(self: Self, url: [:0]const u8) !Response {
return self.perform();
}

// /// Post issues a POST to the specified URL.
/// Post issues a POST to the specified URL.
pub fn post(self: Self, url: [:0]const u8, content_type: []const u8, body: []const u8) !Response {
var buf = Buffer.init(self.allocator);
try self.setWritefunction(bufferWriteCallback);
Expand All @@ -275,6 +308,21 @@ pub fn post(self: Self, url: [:0]const u8, content_type: []const u8, body: []con
return resp;
}

/// Upload issues a PUT request to upload file.
pub fn upload(self: Self, url: [:0]const u8, path: []const u8) !Response {
var up = try Upload.init(path);
defer up.deinit();

try self.setUpload(&up);
var buf = Buffer.init(self.allocator);
try self.setWritefunction(bufferWriteCallback);
try self.setWritedata(&buf);
try self.setUrl(url);
var resp = try self.perform();
resp.body = buf;
return resp;
}

/// Used for write response via `Buffer` type.
// https://curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
// size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
Expand Down

0 comments on commit cef1dc2

Please sign in to comment.