commit 28a534d74d397b350a9faea8817e3db29ed763af Author: Abhinav Sarkar Date: Sat Jul 21 09:12:42 2018 +0530 Intial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40f3432 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.stack-work/ +abhinavsarkar-net-server.cabal +*~ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ba7d470 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM haskell:8.4.3 as builder +WORKDIR /opt/hastatic +ADD . . +RUN stack install && strip /root/.local/bin/hastatic + +FROM fpco/haskell-scratch:integer-gmp +COPY --from=builder /root/.local/bin/hastatic /usr/bin/hastatic \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..825d1ff --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +Copyright Abhinav Sarkar (c) 2018 + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of Abhinav Sarkar nor the names of other + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..23a4f79 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# hastatic + +_hastatic_ is a tiny static web server for Docker. + +## Features + +- A tiny web server, just 3MB in size. +- Statically compiled binary with no dependencies. +- Built for Docker. +- Supports custom 404 file. +- Supports custom index files for URLs ending with "/". +- Adds caching headers automatically. +- Does not support HTTPS. It is expected that you run it behind a reverse-proxy server with HTTPS support, like nginx. + +## Usage + +Create a Dockerfile for your website, deriving from `abhin4v/hastatic`: + +```dockerfile +FROM abhin4v/hastatic:latest + +COPY mywebsite /opt/mywebsite +WORKDIR /opt/mywebsite +CMD ["/usr/bin/hastatic"] +``` + +Build and run: + +``` +$ docker build -t mywebsite . +$ docker run -p 8080:3000 mywebsite # run with default configs +$ docker run -e PORT=2000 -e NF_FILE=404.html -e IDX_FILE=index.html -p 8080:2000 mywebsite +``` + +## Configuration + +The Docker image supports these environment variable for configuration: + +- PORT: the port to run the web server on, default: 3000 +- NF_FILE: name of the custom 404 file, default: `404.html` +- IDX_FILE: name of the custom index files, default: `index.html` + +## Internals + +_hastatic_ is written in Haskell, just 30 lines of it. It uses the excellent [Warp](https://hackage.haskell.org/package/warp) server underneath. diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/hastatic.cabal b/hastatic.cabal new file mode 100644 index 0000000..62be457 --- /dev/null +++ b/hastatic.cabal @@ -0,0 +1,34 @@ +-- This file has been generated from package.yaml by hpack version 0.28.2. +-- +-- see: https://github.com/sol/hpack +-- +-- hash: e49204f2f26a2ed8552e95f4f543b1cb4148cc509790f5d550ab98b70a722323 + +name: hastatic +version: 0.9.0 +description: Please see the README on GitHub at +author: Abhinav Sarkar +maintainer: abhinav@abhinavsarkar.net +copyright: 2018 Abhinav Sarkar +license: BSD3 +license-file: LICENSE +build-type: Simple +cabal-version: >= 1.10 +extra-source-files: + README.md + +executable hastatic + main-is: Main.hs + other-modules: + Paths_hastatic + hs-source-dirs: + src + ghc-options: -threaded -rtsopts -with-rtsopts=-N -O2 + build-depends: + base >=4.7 && <5 + , http-types + , text + , wai + , wai-middleware-static + , warp + default-language: Haskell2010 diff --git a/package.yaml b/package.yaml new file mode 100644 index 0000000..70837ad --- /dev/null +++ b/package.yaml @@ -0,0 +1,30 @@ +name: hastatic +version: 0.9.0 +license: BSD3 +author: "Abhinav Sarkar" +maintainer: "abhinav@abhinavsarkar.net" +copyright: "2018 Abhinav Sarkar" + +extra-source-files: +- README.md + +description: Please see the README on GitHub at + +dependencies: +- base >= 4.7 && < 5 + +executables: + hastatic: + main: Main.hs + source-dirs: src + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + - -O2 + dependencies: + - wai + - warp + - http-types + - wai-middleware-static + - text diff --git a/src/Main.hs b/src/Main.hs new file mode 100644 index 0000000..c96593a --- /dev/null +++ b/src/Main.hs @@ -0,0 +1,43 @@ +{-# LANGUAGE OverloadedStrings #-} +module Main where + +import Data.Maybe (fromMaybe) +import qualified Data.List as List +import qualified Data.Text as T +import Network.Wai +import Network.Wai.Middleware.Static +import Network.HTTP.Types (status404) +import Network.Wai.Handler.Warp (run) +import System.Environment (lookupEnv) +import Text.Read (readMaybe) + +indexHTML :: T.Text -> Middleware +indexHTML indexFile app req respond = + let path = pathInfo req + in flip app respond $ req { pathInfo = fixPath path } + where + fixPath path = case path of + [] -> [indexFile] + [""] -> [indexFile] + [x] -> if "." `T.isInfixOf` x then [x] else [x, indexFile] + (x:xs) -> x : fixPath xs + +notFoundHandler notFoundFile _ respond = respond $ + responseFile status404 [("Content-Type", "text/html")] notFoundFile Nothing + +main = do + mPort <- lookupEnv "PORT" + let port = fromMaybe 3000 (readMaybe =<< mPort) + mNotFoundFile <- lookupEnv "NF_FILE" + let notFoundFile = fromMaybe "404.html" mNotFoundFile + mIndexFile <- lookupEnv "IDX_FILE" + let indexFile = T.pack $ fromMaybe "index.html" mIndexFile + + cache <- initCaching PublicStaticCaching + putStrLn $ "Starting server on port: " <> show port + run port + $ indexHTML indexFile + $ staticPolicy' cache (predicate noDot) + $ notFoundHandler notFoundFile + where + noDot = not . List.isPrefixOf "." diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..79c0d92 --- /dev/null +++ b/stack.yaml @@ -0,0 +1,4 @@ +resolver: lts-12.1 + +packages: +- .