initial commit
This commit is contained in:
commit
7cd3c0a4d2
11 changed files with 3521 additions and 0 deletions
0
.env
Normal file
0
.env
Normal file
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/node_modules
|
1837
Cargo.lock
generated
Normal file
1837
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
23
Cargo.toml
Normal file
23
Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "crashdump"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
worker = "0.0.18"
|
||||
symbolic = {version = "12.8.0", features = ["debuginfo"] }
|
||||
debugid = "0.8.0"
|
||||
url = "2.5.0"
|
||||
sourcemap = "8.0.1"
|
||||
serde = { version = "1.0.199", features = ["serde_derive"] }
|
||||
axohtml = "0.5.0"
|
||||
base64 = "0.22.0"
|
||||
|
||||
[profile.release]
|
||||
opt-level = "s" # optimize for size in release builds
|
||||
lto = true
|
||||
strip = true
|
||||
codegen-units = 1
|
86
README.md
Normal file
86
README.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Template: worker-rust
|
||||
|
||||
[![Deploy with Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudflare/workers-sdk/tree/main/templates/experimental/worker-rust)
|
||||
|
||||
A template for kick starting a Cloudflare worker project using [`workers-rs`](https://github.com/cloudflare/workers-rs).
|
||||
|
||||
This template is designed for compiling Rust to WebAssembly and publishing the resulting worker to Cloudflare's [edge infrastructure](https://www.cloudflare.com/network/).
|
||||
|
||||
## Setup
|
||||
|
||||
To create a `my-project` directory using this template, run:
|
||||
|
||||
```sh
|
||||
$ npx wrangler generate my-project https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust
|
||||
# or
|
||||
$ yarn wrangler generate my-project https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust
|
||||
# or
|
||||
$ pnpm wrangler generate my-project https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust
|
||||
```
|
||||
|
||||
## Wrangler
|
||||
|
||||
Wrangler is used to develop, deploy, and configure your Worker via CLI.
|
||||
|
||||
Further documentation for Wrangler can be found [here](https://developers.cloudflare.com/workers/tooling/wrangler).
|
||||
|
||||
## Usage
|
||||
|
||||
This template starts you off with a `src/lib.rs` file, acting as an entrypoint for requests hitting your Worker. Feel free to add more code in this file, or create Rust modules anywhere else for this project to use.
|
||||
|
||||
With `wrangler`, you can build, test, and deploy your Worker with the following commands:
|
||||
|
||||
```sh
|
||||
# run your Worker in an ideal development workflow (with a local server, file watcher & more)
|
||||
$ npm run dev
|
||||
|
||||
# deploy your Worker globally to the Cloudflare network (update your wrangler.toml file for configuration)
|
||||
$ npm run deploy
|
||||
```
|
||||
|
||||
Read the latest `worker` crate documentation here: https://docs.rs/worker
|
||||
|
||||
## Advanced Example
|
||||
|
||||
As this template comprises only the essential setup, we recommend considering our advanced example to leverage its additional functionalities. The advanced example showcases the creation of multiple routes, logging of requests, retrieval of field data from a form, and other features that may prove useful to your project.
|
||||
The following example has been taken from: [workers-rs](https://github.com/cloudflare/workers-rs). You can learn more about how to use workers with rust by going there.
|
||||
|
||||
```rust
|
||||
use worker::*;
|
||||
|
||||
#[event(fetch)]
|
||||
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
|
||||
console_log!(
|
||||
"{} {}, located at: {:?}, within: {}",
|
||||
req.method().to_string(),
|
||||
req.path(),
|
||||
req.cf().coordinates().unwrap_or_default(),
|
||||
req.cf().region().unwrap_or("unknown region".into())
|
||||
);
|
||||
|
||||
if !matches!(req.method(), Method::Post) {
|
||||
return Response::error("Method Not Allowed", 405);
|
||||
}
|
||||
|
||||
if let Some(file) = req.form_data().await?.get("file") {
|
||||
return match file {
|
||||
FormEntry::File(buf) => {
|
||||
Response::ok(&format!("size = {}", buf.bytes().await?.len()))
|
||||
}
|
||||
_ => Response::error("`file` part of POST form must be a file", 400),
|
||||
};
|
||||
}
|
||||
|
||||
Response::error("Bad Request", 400)
|
||||
}
|
||||
```
|
||||
|
||||
## WebAssembly
|
||||
|
||||
`workers-rs` (the Rust SDK for Cloudflare Workers used in this template) is meant to be executed as compiled WebAssembly, and as such so **must** all the code you write and depend upon. All crates and modules used in Rust-based Workers projects have to compile to the `wasm32-unknown-unknown` triple.
|
||||
|
||||
Read more about this on the [`workers-rs`](https://github.com/cloudflare/workers-rs) project README.
|
||||
|
||||
## Issues
|
||||
|
||||
If you have any problems with the `worker` crate, please open an issue on the upstream project issue tracker on the [`workers-rs` repository](https://github.com/cloudflare/workers-rs).
|
1280
package-lock.json
generated
Normal file
1280
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
12
package.json
Normal file
12
package.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "template-worker-rust",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev --env dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.1.2"
|
||||
}
|
||||
}
|
3
rust-toolchain
Normal file
3
rust-toolchain
Normal file
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
channel = "1.75"
|
||||
components = [ "rustfmt", "clippy", "llvm-tools-preview", "rust-src" ]
|
231
src/lib.rs
Normal file
231
src/lib.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
use std::fmt::{self, format, Display, UpperHex};
|
||||
|
||||
use axohtml::{dom::DOMTree, html, text, types::Metadata};
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
|
||||
use debugid::{DebugId, ParseDebugIdError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use symbolic::*;
|
||||
use worker::*;
|
||||
|
||||
#[event(fetch)]
|
||||
async fn main(req: Request, env: Env, ctx: Context) -> Result<Response> {
|
||||
console_log!("Received {:?}", req);
|
||||
match req.method() {
|
||||
Method::Head => todo!(),
|
||||
Method::Get => {
|
||||
let resp = get_async(req, ctx).await;
|
||||
match resp {
|
||||
Ok(r) => Ok(r),
|
||||
Err(e) => {
|
||||
console_log!("Error: {:?}", e);
|
||||
Response::error(format!("{:?}", e), 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
Method::Post => todo!(),
|
||||
Method::Put => todo!(),
|
||||
Method::Patch => todo!(),
|
||||
Method::Delete => todo!(),
|
||||
Method::Options => todo!(),
|
||||
Method::Connect => todo!(),
|
||||
Method::Trace => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_async(req: Request, ctx: Context) -> worker::Result<Response> {
|
||||
let decoded = decode_crashdump_url(&req.url()?)?;
|
||||
let addrs = decoded
|
||||
.addrs
|
||||
.iter()
|
||||
.map(|x| format!("{:#X} | {}", x, x).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
// msg is base64 encoded
|
||||
let msg = req.url()?;
|
||||
let msg = msg.query().unwrap_or("");
|
||||
let msg = URL_SAFE_NO_PAD
|
||||
.decode(msg.as_bytes())
|
||||
.map_err(|err| worker::Error::RustError(format!("{err} {msg}")))?;
|
||||
let msg = String::from_utf8(msg)
|
||||
.map_err(|err| worker::Error::RustError(format!("{err} {:?}", req)))?;
|
||||
|
||||
let mut doc: DOMTree<String> = html!(
|
||||
<html>
|
||||
<head>
|
||||
<title>"crashdump"</title>
|
||||
<meta name=Metadata::Author content="sevki"/>
|
||||
<link rel="stylesheet" href="https://oknotok.computer/notokcss.css"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
// favicon is http://oknotok.computer/crashdump-it-s-not-u.png
|
||||
<link rel="icon" type="image/png" href="http://oknotok.computer/crashdump-it-s-not-u.png"/>
|
||||
</head>
|
||||
<body class="is-dark" style="width: 800px; margin: 0 auto;">
|
||||
<h1 style="display: flex; justify-content: center; align-items: center;">
|
||||
<img
|
||||
src="http://oknotok.computer/crashdump-it-s-not-u.png"
|
||||
class="nes-avata is-large"
|
||||
style="width: 150px; height: 150px; margin-bottom: -20px;"
|
||||
alt="it's not you, it's we" />
|
||||
"crashdump"
|
||||
</h1>
|
||||
<p class="official">
|
||||
</p>
|
||||
<div class="nes-container is-dark">
|
||||
<p>{text!("Platform: {}", decoded.platform)}</p>
|
||||
<p>{text!("Arch: {}", decoded.arch)}</p>
|
||||
<p>{text!("DebugId: {}", decoded.debug_id.breakpad())}</p>
|
||||
<ul>
|
||||
{addrs.iter().map(|x| html!(<li>{text!("{}", x)}</li>))}
|
||||
</ul>
|
||||
</div>
|
||||
{if let Some(ref query) = req.url().unwrap().query() {
|
||||
html!(<p class="nes-balloon from-left">
|
||||
{text!("Query: {}", msg)}
|
||||
</p>)
|
||||
} else {
|
||||
html!(<p>{text!("")}</p>)
|
||||
}}
|
||||
|
||||
<footer>
|
||||
<small>"this is a work in progress, please report any issues to @sevki"
|
||||
"inspired by " <a href="https://bun.sh/blog/bun-report-is-buns-new-crash-reporter/">"bun.report"</a>
|
||||
</small>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
let doc_str = doc.to_string();
|
||||
return Response::from_html(doc_str);
|
||||
}
|
||||
|
||||
pub async fn crash(mut req: Request, _ctx: RouteContext<()>) -> worker::Result<Response> {
|
||||
console_log!("Received {:?}", req);
|
||||
let bytes = req.bytes().await?;
|
||||
console_log!("Received {} bytes", bytes.len());
|
||||
return Response::ok("obj.debug_id().to_string()");
|
||||
|
||||
let obj = symbolic::debuginfo::elf::ElfObject::parse(bytes.as_slice()).unwrap();
|
||||
Response::ok(obj.debug_id().to_string())
|
||||
}
|
||||
|
||||
// decode_crashdump_url decodes a crashdump URL and returns the
|
||||
// addresses, debugId, buildId, commit and cargo package meta.
|
||||
fn decode_crashdump_str(url: &str) -> (Vec<i64>, DebugId) {
|
||||
let u = url::Url::parse(url).unwrap();
|
||||
let segments = u.path_segments().unwrap().collect::<Vec<_>>();
|
||||
let x = segments[2].to_uppercase();
|
||||
let x = x.as_str();
|
||||
let debug_id = DebugId::from_breakpad(x).expect("da");
|
||||
let addrs = sourcemap::vlq::parse_vlq_segment(segments[3]).unwrap();
|
||||
(addrs, debug_id)
|
||||
}
|
||||
/*
|
||||
"windows" => "🪟",
|
||||
"freebsd" => "freebsd",
|
||||
"dragonfly" => "dragonfly",
|
||||
"netbsd" => "netbsd",
|
||||
"openbsd" => "openbsd",
|
||||
"solaris" => "solaris",
|
||||
"illumos" => "illumos",
|
||||
"android" => "🤖",
|
||||
"ios" => "🍏",
|
||||
*/
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct TinyDebugInfo {
|
||||
debug_id: DebugId,
|
||||
addrs: Vec<i64>,
|
||||
platform: String,
|
||||
arch: String,
|
||||
}
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
enum CrashDumpError {
|
||||
InvalidUrl,
|
||||
InvalidPlatform,
|
||||
InvalidDebugId,
|
||||
InvalidAddrs,
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for CrashDumpError {
|
||||
fn from(_: url::ParseError) -> Self {
|
||||
CrashDumpError::InvalidUrl
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CrashDumpError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
CrashDumpError::InvalidUrl => write!(f, "Invalid URL"),
|
||||
CrashDumpError::InvalidPlatform => write!(f, "Invalid Platform"),
|
||||
CrashDumpError::InvalidDebugId => write!(f, "Invalid DebugId"),
|
||||
CrashDumpError::InvalidAddrs => write!(f, "Invalid Addrs"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
"x86" => "✖️",
|
||||
"x86_64" => "♔", // chess board has 64squares
|
||||
"aarch64" => "🦾",
|
||||
"arm" => "💪",
|
||||
*/
|
||||
|
||||
// decode_crashdump_url decodes a crashdump URL and returns the
|
||||
// addresses, debugId, buildId, commit and cargo package meta.
|
||||
fn decode_crashdump_url(url: &url::Url) -> Result<TinyDebugInfo> {
|
||||
let segments = url.path_segments().unwrap().collect::<Vec<_>>();
|
||||
if segments.len() < 4 {
|
||||
return Ok(TinyDebugInfo {
|
||||
debug_id: DebugId::nil(),
|
||||
addrs: vec![
|
||||
3549216002486605715,
|
||||
2559278670753769365,
|
||||
182467902083143991,
|
||||
5838215469077906834,
|
||||
3492565976812494985,
|
||||
1098047980449583976,
|
||||
8099609723558978835,
|
||||
2892162414157202502,
|
||||
672449775854363957,
|
||||
1066679607773740756,
|
||||
],
|
||||
platform: "plan9".to_string(),
|
||||
arch: "riscv".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let platform = match segments[0] {
|
||||
"%F0%9F%90%A7" /* 🐧 */ => "linux",
|
||||
"%F0%9F%8D%8E" /* 🍎 */ => "macos",
|
||||
// "windows" => "🪟",
|
||||
"%F0%9F%AA%9F" /* 🪟 */ => "windows",
|
||||
// ios
|
||||
"%F0%9F%8D%8F" /* 🍏 */ => "ios",
|
||||
// android
|
||||
"%F0%9F%A4%96" /* 🤖 */ => "android",
|
||||
_ => segments[0],
|
||||
};
|
||||
|
||||
let arch = match segments[1] {
|
||||
"%E2%99%94" /* ♔ */ => "x64", // chess board has 64 squares
|
||||
// "aarch64" => "🦾",
|
||||
"%F0%9F%A6%BE" /* 🦾 */ => "aarch64",
|
||||
// "arm" => "💪",
|
||||
"%F0%9F%92%AA" /* 💪 */ => "arm",
|
||||
// "x86" => "✖️",
|
||||
"%E2%9C%96" /* ✖️ */ => "x86",
|
||||
_ => segments[1],
|
||||
};
|
||||
|
||||
let x = segments[2].to_uppercase();
|
||||
let x = x.as_str();
|
||||
let debug_id =
|
||||
DebugId::from_breakpad(x).map_err(|err| worker::Error::RustError(format!("{err}")))?;
|
||||
let addrs = sourcemap::vlq::parse_vlq_segment(&segments[3..].join("/"))
|
||||
.map_err(|err| worker::Error::RustError(format!("{:?} {err}", segments)))?;
|
||||
|
||||
Ok(TinyDebugInfo {
|
||||
debug_id,
|
||||
addrs,
|
||||
platform: platform.to_string(),
|
||||
arch: arch.to_string(),
|
||||
})
|
||||
}
|
9
wrangler.toml
Normal file
9
wrangler.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
name = "crashdump"
|
||||
main = "build/worker/shim.mjs"
|
||||
compatibility_date = "2023-12-01"
|
||||
|
||||
[build]
|
||||
command = "cargo install -q worker-build && worker-build --release"
|
||||
|
||||
[env.dev]
|
||||
build = { command = "cargo install -q worker-build && worker-build --dev" }
|
38
yarn-error.log
Normal file
38
yarn-error.log
Normal file
|
@ -0,0 +1,38 @@
|
|||
Arguments:
|
||||
/nix/store/f82z6nvdg5iggrb14pm5yrv7y13i0811-nodejs-18.19.1/bin/node /etc/profiles/per-user/sevki/bin/yarn install
|
||||
|
||||
PATH:
|
||||
/nix/store/51y4c8dabk0f3whjra2mwhl138rbgbnh-glib-2.78.4-bin/bin:/home/sevki/.cargo/bin:/run/wrappers/bin:~/.rustup:~/.cargo/bin:~/.deno/bin:~/.yarn/bin:~/go/bin:nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels:/var/lib/flatpak/exports/share:/home/sevki/.local/share/flatpak/exports/share:~/.rustup/toolchains/1.76.0-x86_64-unknown-linux-gnu/bin/:~/.rustup/toolchains/1.77.0-x86_64-unknown-linux-gnu/bin/:/home/sevki/.local/share/flatpak/exports/bin:/var/lib/flatpak/exports/bin:/home/sevki/.nix-profile/bin:/nix/profile/bin:/home/sevki/.local/state/nix/profile/bin:/etc/profiles/per-user/sevki/bin:/nix/var/nix/profiles/default/bin:/run/current-system/sw/bin
|
||||
|
||||
Yarn version:
|
||||
1.22.19
|
||||
|
||||
Node version:
|
||||
18.19.1
|
||||
|
||||
Platform:
|
||||
linux x64
|
||||
|
||||
Trace:
|
||||
Error: getaddrinfo EAI_AGAIN registry.yarnpkg.com
|
||||
at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:107:26)
|
||||
|
||||
npm manifest:
|
||||
{
|
||||
"name": "template-worker-rust",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"deploy": "wrangler deploy",
|
||||
"dev": "wrangler dev --env dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
yarn manifest:
|
||||
No manifest
|
||||
|
||||
Lockfile:
|
||||
No lockfile
|
Loading…
Reference in a new issue