Skip to content

Commit

Permalink
Merge pull request #8 from phated/main
Browse files Browse the repository at this point in the history
  • Loading branch information
Radu M authored Oct 22, 2021
2 parents af3d878 + 3a383e7 commit ec6aafd
Show file tree
Hide file tree
Showing 10 changed files with 374 additions and 157 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/grain.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- uses: engineerd/[email protected]
with:
name: "grain"
url: "https://github.com/grain-lang/grain/releases/download/grain-v0.3.2/grain-linux-x64"
url: "https://github.com/grain-lang/grain/releases/download/grain-v0.4.3/grain-linux-x64"
- uses: engineerd/[email protected]
with:
name: "wasmtime"
Expand Down
14 changes: 13 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,16 @@ test:

.PHONY: push
push:
hippofactory -s ${BINDLE_SERVER_URL} .
hippofactory -s ${BINDLE_SERVER_URL} .

doc: lib/*.gr
grain doc lib/env.gr -o lib/env.md
grain doc lib/mediatype.gr -o lib/mediatype.md
grain doc lib/stringutil.gr -o lib/stringutil.md

fmt: *.gr lib/*.gr
grain format fileserver.gr --in-place
grain format tests.gr --in-place
grain format lib/env.gr --in-place
grain format lib/mediatype.gr --in-place
grain format lib/stringutil.gr --in-place
116 changes: 70 additions & 46 deletions fileserver.gr
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,86 @@ import Map from "map"
import Option from "option"
import File from "sys/file"
import String from "string"
import Result from "result"
import Mediatype from "./lib/mediatype"
import Stringutil from "./lib/stringutil"

let serve = (abs_path) => {
// Trim the leading /
let path = String.slice(1, String.length(abs_path), abs_path)
File.fdWrite(File.stderr, "Fileserver: Loading file ")
File.fdWrite(File.stderr, path)
File.fdWrite(File.stderr, "\n")
// Utility wrapper around a Result.expect that ignores the return value
// so we don't need to worry about things returning non-Void types
let validateResult = (msg, res) => {
ignore(Result.expect(msg, res))
}

let internalError = () => {
validateResult(
"Unexpected error when writing Internal Server Error response",
File.fdWrite(File.stdout, "Status: 500\n\nInternal Server Error"),
)
}

// Open file
// TODO: This throws an error when something goes wrong.
// But Grain 0.3 does not have a try/catch yet.
// When we figure out how, we need to catch the error here
// and do a 404 or 500.
let input = File.pathOpen(
File.pwdfd,
[],
path,
[],
[File.FdRead],
[],
[],
)
let notFound = () => {
validateResult(
"Unexpected error when writing Not Found response",
File.fdWrite(File.stdout, "Status: 404\n\nNot Found"),
)
}

File.fdWrite(File.stdout, "Content-Type: ")
File.fdWrite(File.stdout, Mediatype.guess(path))
File.fdWrite(File.stdout, "\n\n")
// Pipe output to STDOUT
let rec pipe = (in, out) => {
let (d, len) = File.fdRead(in, 1024)
File.fdWrite(out, d)
if (len > 0) {
pipe(in, out)
}
}
pipe(input, File.stdout)
File.fdClose(input)
// Pipe output to STDOUT
let rec pipe = (in, out) => {
let res = File.fdRead(in, 1024)
match (res) {
Err(err) => Err(err),
Ok((d, len)) => {
let res = File.fdWrite(out, d)
if (len > 0) {
pipe(in, out)
} else {
res
}
},
}
}

let guestpath = (env) => {
let serve = abs_path => {
// Trim the leading /
let path = String.slice(1, String.length(abs_path), abs_path)
// Explicitly ignoring any Ok or Err that happens on this log
// The `ignore` can be removed if you don't want to be explicit about this behavior
ignore(File.fdWrite(File.stderr, "Fileserver: Loading file " ++ path ++ "\n"))

// Open file
let result = File.pathOpen(File.pwdfd, [], path, [], [File.FdRead], [], [])

// Backward compat for an older version of Wagi that had PATH_INFO wrong.
// X_RELATIVE_PATH was removed before Wagi 0.4
match (Map.get("X_RELATIVE_PATH", env)) {
Some(p) => String.concat("/", p),
None => {
Option.unwrap(Map.get("PATH_INFO", env))
}
}

match (result) {
Err(_err) => notFound(),
Ok(input) => {
validateResult(
"Unexpected error when writing Content-Type",
File.fdWrite(
File.stdout,
"Content-Type: " ++ Mediatype.guess(path) ++ "\n\n",
),
)

validateResult(
"Unexpected error when streaming file body",
pipe(input, File.stdout),
)
// This validation may be able to be removed if it doesn't matter if the fdClose fails
validateResult("Unexpected error when closing file", File.fdClose(input))
},
}
}

let notFound = () => {
File.fdWrite(File.stdout, "Status: 404\n\nNot Found")
let guestpath = env => {
// Backward compat for an older version of Wagi that had PATH_INFO wrong.
// X_RELATIVE_PATH was removed before Wagi 0.4
match (Map.get("X_RELATIVE_PATH", env)) {
Some(p) => String.concat("/", p),
None => {
Option.unwrap(Map.get("PATH_INFO", env))
},
}
}

let kv = Env.envMap()
Expand Down
59 changes: 35 additions & 24 deletions lib/env.gr
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,44 @@ import Array from "array"
import String from "string"
import Option from "option"

// Split an environment variable at the first equals sign.
// @param item: An environment variable pair, separated by an equals sign (=).
// @return (String, String) A tuple key/value pair.
export let splitEnvVar = (item) => {
let offsetOpt = String.indexOf("=", item)
/**
* Split an environment variable at the first equals sign.
*
* @param item: An environment variable pair, separated by an equals sign (=).
* @returns A tuple key/value pair.
*/
export let splitEnvVar = item => {
let offsetOpt = String.indexOf("=", item)

// For now, fail if the env var is malformed.
let offset = Option.unwrap(offsetOpt)
// For now, fail if the env var is malformed.
let offset = Option.unwrap(offsetOpt)

let key = String.slice(0, offset, item)
let val = String.slice(offset+1, String.length(item), item)
(key, val)
let key = String.slice(0, offset, item)
let val = String.slice(offset + 1, String.length(item), item)
(key, val)
}

// Get the environment variables as a Map<String, String>
//
// @return Map<String, String> A map of all environment variables.
export let envMap = () => {
let parsed = Map.make()
let env = Process.env()
let pairs = Array.map(splitEnvVar,env)
/**
* Get the environment variables as a Map<String, String>
*
* @returns A map of all environment variables.
*/
export let envMap = () => {
let parsed = Map.make()
let env = Process.env()
match (env) {
Err(err) => parsed,
Ok(env) => {
let pairs = Array.map(splitEnvVar, env)

Array.forEach((item) => {
let (k, v) = item
Map.set(k, v, parsed)
}, pairs)
parsed
Array.forEach(
item => {
let (k, v) = item
Map.set(k, v, parsed)
},
pairs,
)
parsed
},
}
}


34 changes: 34 additions & 0 deletions lib/env.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
### Env.**splitEnvVar**

```grain
splitEnvVar : String -> (String, String)
```

Split an environment variable at the first equals sign.

Parameters:

|param|type|description|
|-----|----|-----------|
|`item`|`String`|An environment variable pair, separated by an equals sign (=).|

Returns:

|type|description|
|----|-----------|
|`(String, String)`|A tuple key/value pair.|

### Env.**envMap**

```grain
envMap : () -> Map.Map<a, b>
```

Get the environment variables as a Map<String, String>

Returns:

|type|description|
|----|-----------|
|`Map.Map<a, b>`|A map of all environment variables.|

50 changes: 32 additions & 18 deletions lib/mediatype.gr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import String from "string"
import Array from "array"
import Option from "option"
import Map from "map"
import {lastIndexOf, reverse} from "./lib/stringutil"
import { lastIndexOf, reverse } from "./stringutil"

export let default_mt = "application/octet-stream"

Expand Down Expand Up @@ -82,18 +82,30 @@ Map.set("7z", "application/x-7z-compressed", mediatypes)
Map.set("azw", "application/vnd.amazon.ebook", mediatypes)
Map.set("bin", "application/octet-stream", mediatypes)
Map.set("doc", "application/msword", mediatypes)
Map.set("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", mediatypes)
Map.set(
"docx",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
mediatypes,
)
Map.set("epub", "application/epub+zip", mediatypes)
Map.set("odp", "application/vnd.oasis.opendocument.presentation", mediatypes)
Map.set("ods", "application/vnd.oasis.opendocument.spreadsheet", mediatypes)
Map.set("odt", "application/vnd.oasis.opendocument.text", mediatypes)
Map.set("pdf", "application/pdf", mediatypes)
Map.set("ppt", "application/vnd.ms-powerpoint", mediatypes)
Map.set("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation", mediatypes)
Map.set(
"pptx",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
mediatypes,
)
Map.set("rtf", "application/rtf", mediatypes)
Map.set("vsd", "application/vnd.visio", mediatypes)
Map.set("xls", "application/vnd.ms-excel", mediatypes)
Map.set("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", mediatypes)
Map.set(
"xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
mediatypes,
)

// Fonts
Map.set("eot", "application/vnd.ms-fontobject", mediatypes)
Expand All @@ -102,19 +114,21 @@ Map.set("ttf", "font/ttf", mediatypes)
Map.set("woff", "font/woff", mediatypes)
Map.set("woff2", "font/woff2", mediatypes)

// Guess the media type of this file
//
// Per recommendation, if no media type is found for an extension,
// this returns `application/octet-stream`.
//
// @param filename: The name of the file
// @returns String a media type
/**
* Guess the media type of this file
*
* Per recommendation, if no media type is found for an extension,
* this returns `application/octet-stream`.
*
* @param filename: The name of the file
* @returns A media type
*/
export let guess = (filename: String) => {
match (lastIndexOf(".", filename)) {
Some(extOffset) => {
let ext = String.slice(extOffset + 1, String.length(filename), filename)
Option.unwrapWithDefault(default_mt, Map.get(ext, mediatypes))
},
None => default_mt
}
match (lastIndexOf(".", filename)) {
Some(extOffset) => {
let ext = String.slice(extOffset + 1, String.length(filename), filename)
Option.unwrapWithDefault(default_mt, Map.get(ext, mediatypes))
},
None => default_mt,
}
}
29 changes: 29 additions & 0 deletions lib/mediatype.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
### Mediatype.**default_mt**

```grain
default_mt : String
```

### Mediatype.**guess**

```grain
guess : String -> String
```

Guess the media type of this file

Per recommendation, if no media type is found for an extension,
this returns `application/octet-stream`.

Parameters:

|param|type|description|
|-----|----|-----------|
|`filename`|`String`|The name of the file|

Returns:

|type|description|
|----|-----------|
|`String`|A media type|

Loading

0 comments on commit ec6aafd

Please sign in to comment.