Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bindings/nodejs): Support presign #1772

Merged
merged 3 commits into from
Mar 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions bindings/nodejs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ corepack enable

`corepack` is distributed with Node.js, so you do not need to specifically look for a way to install it.


### Build

```bash
Expand All @@ -62,9 +61,6 @@ yarn test

We use [`Cucumber`](https://cucumber.io/) for behavior testing. Refer to [here](https://cucumber.io/docs/guides/overview/) for more information about `Cucumber`.



## License

Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0

49 changes: 49 additions & 0 deletions bindings/nodejs/examples/presign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

const http = require('node:http')
const url = require('node:url')
const { Operator } = require('../index')

const op = new Operator('s3', {
root: '/',
bucket: 'example-bucket',
})

const server = http.createServer(async (req, res) => {
res.setHeader('Content-Type', 'text/json; charset=utf-8')

if (req.url.startsWith('/presign') && req.method === 'GET') {
const urlParts = url.parse(req.url, true)
const path = urlParts.query.path
const expires = urlParts.query.expires

const presignedRequest = op.presignRead(path, parseInt(expires))
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved

res.statusCode = 200
res.end(JSON.stringify({ url: presignedRequest.uri }))
} else {
res.statusCode = 404
res.end('Not Found')
}
})

server.listen(3000, () => {
console.log('Server is listening on port 3000.')
})
9 changes: 8 additions & 1 deletion bindings/nodejs/generated.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
* under the License.
*/

/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */

/* auto-generated by NAPI-RS */

const { existsSync, readFileSync } = require('fs')
const { join } = require('path')

Expand Down Expand Up @@ -265,10 +271,11 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}

const { Operator, Entry, Metadata, Lister, BlockingLister } = nativeBinding
const { Operator, Entry, Metadata, Lister, BlockingLister, PresignedRequest } = nativeBinding

module.exports.Operator = Operator
module.exports.Entry = Entry
module.exports.Metadata = Metadata
module.exports.Lister = Lister
module.exports.BlockingLister = BlockingLister
module.exports.PresignedRequest = PresignedRequest
30 changes: 30 additions & 0 deletions bindings/nodejs/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ export class Operator {
* An error will be returned if given path doesn't end with `/`.
*/
listSync(path: string): BlockingLister
/**
* Get a presigned request for read.
*
* Unit of expires is seconds.
*/
presignRead(path: string, expires: number): PresignedRequest
/**
* Get a presigned request for write.
*
* Unit of expires is seconds.
*/
presignWrite(path: string, expires: number): PresignedRequest
/**
* Get a presigned request for stat.
*
* Unit of expires is seconds.
*/
presignStat(path: string, expires: number): PresignedRequest
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved
}
export class Entry {
/** Return the path of this entry. */
Expand Down Expand Up @@ -101,3 +119,15 @@ export class Lister {
export class BlockingLister {
next(): Entry | null
}
export class PresignedRequest {
/** Returns the HTTP method of this request. */
get method(): string
/** Returns the URI of this request. */
get uri(): string
/**
* Returns the headers of this request.
*
* The key of the map is the header name, and the value is the header value AS bytes.
*/
headers(): Record<string, string>
}
74 changes: 74 additions & 0 deletions bindings/nodejs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use std::str::FromStr;
use futures::TryStreamExt;
use napi::bindgen_prelude::*;
use time::format_description::well_known::Rfc3339;
use time::Duration;

fn build_operator(
scheme: opendal::Scheme,
Expand Down Expand Up @@ -206,6 +207,42 @@ impl Operator {
self.0.blocking().scan(&path).map_err(format_napi_error)?,
))
}

/// Get a presigned request for read.
///
/// Unit of expires is seconds.
#[napi]
pub fn presign_read(&self, path: String, expires: u32) -> Result<PresignedRequest> {
let res = self
.0
.presign_read(&path, Duration::seconds(expires as i64))
.map_err(format_napi_error)?;
Ok(PresignedRequest(res))
}

/// Get a presigned request for write.
///
/// Unit of expires is seconds.
#[napi]
pub fn presign_write(&self, path: String, expires: u32) -> Result<PresignedRequest> {
let res = self
.0
.presign_write(&path, Duration::seconds(expires as i64))
.map_err(format_napi_error)?;
Ok(PresignedRequest(res))
}

/// Get a presigned request for stat.
///
/// Unit of expires is seconds.
#[napi]
pub fn presign_stat(&self, path: String, expires: u32) -> Result<PresignedRequest> {
let res = self
.0
.presign_stat(&path, Duration::seconds(expires as i64))
.map_err(format_napi_error)?;
Ok(PresignedRequest(res))
}
}

#[napi]
Expand Down Expand Up @@ -318,6 +355,43 @@ impl BlockingLister {
}
}

#[napi]
pub struct PresignedRequest(opendal::raw::PresignedRequest);

#[napi]
impl PresignedRequest {
/// Returns the HTTP method of this request.
#[napi(getter)]
pub fn method(&self) -> String {
self.0.method().to_string()
}

/// Returns the URI of this request.
#[napi(getter)]
pub fn uri(&self) -> String {
self.0.uri().to_string()
}

/// Returns the headers of this request.
///
/// The key of the map is the header name, and the value is the header value.
#[napi]
pub fn headers(&self) -> HashMap<String, String> {
self.0
.header()
.iter()
.map(|(k, v)| {
(
k.as_str().to_string(),
v.to_str()
.expect("header value contains non visible ascii characters")
.to_string(),
)
})
.collect()
}
}

fn format_napi_error(err: opendal::Error) -> Error {
Error::from_reason(format!("{}", err))
}