Open8
RustのTIPS
スレッドの中で乱数を使う。
rand = "0.8.5"
use rand::{Rng, SeedableRng};
let mut rng = rand::rngs::StdRng::from_entropy();
let res = rng.gen::<u8>();
minioを使ってS3にアクセス
let credentials_provider = Credentials::new("admin123", "admin123", None, None, "example");
let config = aws_sdk_s3::Config::builder()
.behavior_version_latest()
.credentials_provider(credentials_provider)
.region(Region::new("ap-northeast-1"))
.force_path_style(true)
.endpoint_url("http://minio:9000")
.build();
Client::from_conf(config);
AESとCTRのサンプル
main.rs
use aes::cipher::StreamCipher;
use ctr::cipher::KeyIvInit;
type Aes128Ctr64LE = ctr::Ctr64LE<aes::Aes128>;
// 暗号化
fn encrypt(key: &str, data: &str) -> Vec<u8> {
use rand::{Rng, SeedableRng};
let mut rng = rand::rngs::StdRng::from_entropy();
let iv = rng.gen::<[u8; 16]>();
let mut cipher = Aes128Ctr64LE::new(key.as_bytes().into(), &iv.into());
let mut buf = data.as_bytes().to_vec();
cipher.apply_keystream(&mut buf);
let mut res = iv.to_vec();
res.extend(buf);
res
}
// 復号化
fn decrypt(key: &str, data: &Vec<u8>) -> String {
let mut cipher = Aes128Ctr64LE::new(key.as_bytes().into(), data[0..16].into());
let mut buf2: Vec<u8> = vec![0; data.len() - 16];
if let Err(err) = cipher.apply_keystream_b2b(&data[16..], &mut buf2.as_mut_slice()) {
println!("{}", err);
}
String::from_utf8(buf2.to_vec()).unwrap()
}
fn main() {
let plaintext = "予定表~①💖ハンカクだ";
let key = "0123456701234567";
let enc = encrypt(key, plaintext);
let dec = decrypt(key, &enc);
assert_eq!(plaintext, dec);
println!("{}", dec);
}
CloudflareでOAuth2
Cargo.toml
[package]
name = "cms"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
axum = { version = "0.7", default-features = false, features = ['query', 'json'] }
console_error_panic_hook = { version = "0.1.1" }
tower-cookies = "0.10.0"
tower-service = "0.3.2"
twapi-v2 = { version = "0.14", features = ["rustls-tls", "oauth"], default-features = false }
worker = { version = "0.1.0", features=['http', 'axum'] }
worker-macros = { version = "0.1.0", features = ['http'] }
[profile.release]
opt-level = "s" # optimize for size in release builds
lto = true
strip = true
codegen-units = 1
lib.rs
use std::{collections::HashMap, sync::Arc};
use axum::{extract::{Query, State}, response::{Html, IntoResponse, Json}, routing::get, Router};
use tower_cookies::{Cookie, CookieManagerLayer, Cookies};
use tower_service::Service;
use twapi_v2::{api::{get_2_users_me, BearerAuthentication}, oauth::{TwitterOauth, TwitterScope}};
use worker::*;
pub const PKCE_VERIFIER: &str = "pkce_verifier";
pub struct AppState {
pub twitter_oauth: TwitterOauth,
}
fn router(env: &Env) -> Router {
let twitter_oauth = oauth_client(&env).unwrap();
let app_store = Arc::new(AppState { twitter_oauth });
Router::new()
.route("/", get(root))
.route("/oauth", get(oauth))
.with_state(app_store)
.layer(CookieManagerLayer::new())
}
#[event(fetch)]
async fn fetch(
req: HttpRequest,
env: Env,
_ctx: Context,
) -> Result<axum::http::Response<axum::body::Body>> {
console_error_panic_hook::set_once();
Ok(router(&env).call(req).await?)
}
fn oauth_client(env: &Env) -> Result<TwitterOauth> {
TwitterOauth::new(
&env.secret("CLIENT_ID").unwrap().to_string(),
&env.secret("CLIENT_SECRET").unwrap().to_string(),
&env.var("CALLBACK_URL").unwrap().to_string(),
TwitterScope::all(),
).map_err(|e| Error::RustError(e.to_string()))
}
pub async fn root(cookies: Cookies, State(state): State<Arc<AppState>>,) -> impl IntoResponse {
let res = state.twitter_oauth.oauth_url();
cookies.add(Cookie::new(PKCE_VERIFIER, res.pkce_verifier.clone()));
Html(format!("<a href='{}'>oauth<a>", res.oauth_url)).into_response()
}
#[worker::send]
async fn oauth(cookies: Cookies, State(state): State<Arc<AppState>>, Query(params): Query<HashMap<String, String>>, ) -> impl IntoResponse {
let pkce = cookies.get(PKCE_VERIFIER).unwrap();
let code = params.get("code").unwrap();
let res = state.twitter_oauth
.token(pkce.value(), code)
.await
.unwrap();
println!("{:?}", res);
let auth = BearerAuthentication::new(res.access_token);
let me = get_2_users_me::Api::all().execute(&auth).await.unwrap();
Json(me.0).into_response()
}
GitHubのprivateを参照する方法
VSCodeのrust-analyzerと開発環境のdocker内で利用できるようにする。
Cargo.toml
Cargo.toml
[net]
git-fetch-with-cli = true
[dependencies]
your-crate = { git = "ssh://git@github.com/MyOrg/your-crate.git", branch = "main"}
GitHubからsshのリポジトリをコピーするが、先頭にssh://をつけるのと、ホスト名と組織名が「:」で区切られているので/に変更する。
rust-analyzer
VSCodeのworkspaceファイルに追加する
{
"settings": {
"rust-analyzer.server.extraEnv": {
"CARGO_NET_GIT_FETCH_WITH_CLI": "true"
},
}
}
Docker
環境変数
CARGO_NET_GIT_FETCH_WITH_CLI=true
GIT_SSH_COMMAND='ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
docker-compose
docker-compose.yaml
services:
app:
build:
context: .
dockerfile: Dockerfile.app
secrets:
- host_ssh_key
secrets:
host_ssh_key:
file: ~/.ssh/id_ed25519
Dockerfil.app
FROM rust:1.80
RUN mkdir ~/.ssh && ln -s /run/secrets/host_ssh_key ~/.ssh/id_ed25519
axumのmultipartをファイルに保存する
use tokio::fs::File;
use tokio_util::io::StreamReader;
use futures_util::TryStreamExt;
while let Some(field) = multipart.next_field().await? {
if let Some(content_type) = field.headers().get("content-type") {
if content_type == "text/csv" {
let mut body_reader = StreamReader::new(field.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)));
let mut file = File::create(file_path.clone()).await?;
tokio::io::copy(&mut body_reader, &mut file).await?;
}
}
}
CloudflareのAI Getawayを通してAmazon Bedrockを呼び出す
Cargo.toml
[package]
name = "bedrock"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
aws-config = { version = "1.5.6", features = ["behavior-version-latest"] }
aws-sdk-bedrockruntime = "1.50.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
http = "1.1.0"
main.rs
use aws_config::{BehaviorVersion, Region};
use aws_sdk_bedrockruntime::{
config::{
interceptors::BeforeTransmitInterceptorContextMut, ConfigBag, Intercept, RuntimeComponents,
},
error::BoxError,
types::{ContentBlock, ConversationRole, Message},
Config,
};
use http::Uri;
const HOST_NAME: &str = "gateway.ai.cloudflare.com";
const REGINO_NAME: &str = "us-east-1";
const MODEL_ID: &str = "anthropic.claude-3-5-sonnet-20240620-v1:0";
const CONTENTS: &str = "次の内容を英語にしてください。「こんにちは世界」";
fn make_uri(uri: &str) -> Result<String, BoxError> {
let uri: Uri = uri.parse()?;
Ok(format!(
"https://{}/v1/{}/{}/aws-bedrock/bedrock-runtime/{}{}",
HOST_NAME,
std::env::var("CF_ID").unwrap_or_default(),
std::env::var("CF_NAME").unwrap_or_default(),
REGINO_NAME,
uri.path()
))
}
#[derive(Debug)]
pub struct UriModifierInterceptor;
impl Intercept for UriModifierInterceptor {
fn name(&self) -> &'static str {
"UriModifierInterceptor"
}
fn modify_before_transmit(
&self,
context: &mut BeforeTransmitInterceptorContextMut<'_>,
_runtime_components: &RuntimeComponents,
_cfg: &mut ConfigBag,
) -> Result<(), BoxError> {
let request = context.request_mut();
let headers = request.headers_mut();
headers.append("host", HOST_NAME);
*request.headers_mut() = headers.clone();
request.set_uri(make_uri(request.uri())?)?;
Ok(())
}
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let config = Config::builder()
.behavior_version(BehaviorVersion::latest())
.interceptor(UriModifierInterceptor)
.region(Region::new(REGINO_NAME))
.credentials_provider(aws_config::default_provider::credentials::default_provider().await)
.build();
let client = aws_sdk_bedrockruntime::Client::from_conf(config);
let res = client
.converse()
.model_id(MODEL_ID)
.messages(
Message::builder()
.role(ConversationRole::User)
.content(ContentBlock::Text(CONTENTS.to_string()))
.build()
.map_err(|_| "failed to build message")
.unwrap(),
)
.send()
.await?;
println!("{:?}", res);
Ok(())
}
Google Driveにファイルをアップロードする。
参考Rust rustlsのpanic"no process-level CryptoProvider available -- call CryptoProvider::install_default"
Cargo.toml
[package]
name = "drive"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
http-body-util = "0.1.2"
hyper-rustls = { version = "0.27.3", feaures = ["native-tokio", "http1", "tls12", "logging", "ring"], default-features = false }
google-drive3 = "6.0.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "fs"]}
serde_json = "1"
main.rs
use google_drive3::{
api::File, hyper_util, yup_oauth2::{self, ServiceAccountKey}, DriveHub
};
use http_body_util::BodyExt;
// RUST_BACKTRACE=1 cargo run
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let secret = tokio::fs::read_to_string("secret.json").await?;
let secret: ServiceAccountKey = serde_json::from_str(&secret)?;
let auth = yup_oauth2::ServiceAccountAuthenticator::builder(secret)
.build()
.await
.unwrap();
let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new())
.build(
hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.unwrap()
.https_or_http()
.enable_http1()
.build(),
);
let hub = DriveHub::new(client, auth);
// アップロードは直接指定のフォルダーに権限を設定する
let req = File {
name: Some("test3.csv".to_string()),
mime_type: Some("text/csv".to_string()),
parents: Some(vec!["置きたいフォルダーのID".to_string()]),
..Default::default()
};
let result = hub
.files()
.create(req)
.supports_all_drives(true)
.upload(std::fs::File::open("test.csv").unwrap(), "text/csv".parse().unwrap())
.await?;
println!("{:?}", result);
let body: Vec<u8> = result.0.into_body().collect().await?.to_bytes().into();
let body = String::from_utf8(body).unwrap();
let body: serde_json::Value = serde_json::from_str(&body)?;
println!("{:?}", body);
Ok(())
}