Skip to content

Commit

Permalink
feat(bindings/nodejs): Support presign (#1772)
Browse files Browse the repository at this point in the history
* feat(bindings/nodejs): support presign

Signed-off-by: suyanhanx <[email protected]>

* change header value to string

Signed-off-by: suyanhanx <[email protected]>

* presign example

Signed-off-by: suyanhanx <[email protected]>

---------

Signed-off-by: suyanhanx <[email protected]>
  • Loading branch information
suyanhanx authored Mar 26, 2023
1 parent 51410fb commit 970e3bf
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 5 deletions.
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))

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
}
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))
}

0 comments on commit 970e3bf

Please sign in to comment.