From 17d39fcdc2f0a2fafc959030a1ad14dea5ee99eb Mon Sep 17 00:00:00 2001 From: Connor Prussin Date: Thu, 25 May 2017 12:12:29 -0700 Subject: [PATCH] Initial commit with basic functionality and library structure. --- .bowerrc | 3 ++ .gitignore | 5 +++ CONTRIBUTING.md | 58 +++++++++++++++++++++++++++++++ LICENSE | 20 +++++++++++ Makefile | 34 ++++++++++++++++++ README.md | 52 +++++++++++++++++++++++++++ bower.json | 24 +++++++++++++ lib/HTTPure.purs | 13 +++++++ lib/HTTPure/HTTPureM.purs | 13 +++++++ lib/HTTPure/Request.purs | 25 +++++++++++++ lib/HTTPure/Response.purs | 28 +++++++++++++++ lib/HTTPure/Route.purs | 46 ++++++++++++++++++++++++ lib/HTTPure/Server.purs | 40 +++++++++++++++++++++ test/HTTPure/HTTPureMSpec.purs | 8 +++++ test/HTTPure/IntegrationSpec.purs | 13 +++++++ test/HTTPure/RequestSpec.purs | 18 ++++++++++ test/HTTPure/ResponseSpec.purs | 23 ++++++++++++ test/HTTPure/RouteSpec.purs | 8 +++++ test/HTTPure/ServerSpec.purs | 23 ++++++++++++ test/HTTPureSpec.purs | 23 ++++++++++++ 20 files changed, 477 insertions(+) create mode 100644 .bowerrc create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 bower.json create mode 100644 lib/HTTPure.purs create mode 100644 lib/HTTPure/HTTPureM.purs create mode 100644 lib/HTTPure/Request.purs create mode 100644 lib/HTTPure/Response.purs create mode 100644 lib/HTTPure/Route.purs create mode 100644 lib/HTTPure/Server.purs create mode 100644 test/HTTPure/HTTPureMSpec.purs create mode 100644 test/HTTPure/IntegrationSpec.purs create mode 100644 test/HTTPure/RequestSpec.purs create mode 100644 test/HTTPure/ResponseSpec.purs create mode 100644 test/HTTPure/RouteSpec.purs create mode 100644 test/HTTPure/ServerSpec.purs create mode 100644 test/HTTPureSpec.purs diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..0ce4548 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "output/components" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..404b8d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/output +/.pulp-cache/ +/.psc* +/.purs* +/.psa* diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..467dab6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# HTTPure Contributing Guide + +Welcome to HTTPure! We would love contributions from the community. Please +follow this guide when creating contributions to help make the project better! + +## Logging Issues + +If you find a bug or a place where documentation needs to be improved, or if you +have a feature request, +please +[submit an issue](https://github.com/cprussin/purescript-httpure/issues/new)! In +issues you submit, please be clear, and preferably have code examples indicating +what is broken, needs improvement, or what your requested API should look like. + +## Contributions + +All contributions to this repository should come in the form of pull requests. +All pull requests must be reviewed before being merged. Please follow these +steps for creating a successful PR: + +1. [Create an issue](https://github.com/cprussin/purescript-httpure/issues/new) + for your contribution. +2. [Create a fork](https://github.com/cprussin/purescript-httpure) on github. +3. Create a branch in your fork for your contribution. +4. Add your contribution to the source tree. +5. Run the test suite. All tests MUST pass for a PR to be accepted. +6. Push your code and create a PR on github. Please make sure to reference your + issue number in your PR description. + +Branch all work off the `master` branch. In the future, we will create branches +for specific release series, and `master` will be used for the current stable +release series. + +### Documentation + +For the most part, HTTPure's documentation is intended to be consumed +through [Pursuit](http://pursuit.purescript.org/packages/purescript-httpure). To +this end, documentation should mostly be provided inline in the codebase, and +should follow the same PR process as other commits. + +We also welcome documentation in the form of guides and examples. These should +live in the [docs](docs) directory. Please ensure all guides are written in +markdown format, and all examples are fully-functional and implemented as +self-contained subdirectories under [docs/examples](docs/examples). + +We try to ensure most examples have corresponding integration tests, both to add +additional testing and to ensure that examples we promote remain functional. If +you plan to contribute examples, please take a look +at [IntegrationSpec.purs](test/HTTPure/IntegrationSpec.purs). + +### Code + +Code should follow existing styles and all code should be accompanied with +relevant unit/integration tests. If you fix a bug, write a test. If you write a +new feature, write a test. + +All tests MUST pass for your PR to be accepted. If you break a test, either fix +the test or fix the code. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..336123b --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Connor Prussin + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b967967 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +PULP := pulp +BOWER := bower +BOWERJSON := bower.json +SRCPATH := ./lib +TESTPATH := ./test +OUTPUT := ./output +BUILD := $(OUTPUT)/build +COMPONENTS := $(OUTPUT)/components +DOCS := $(OUTPUT)/docs +TESTMAIN := HTTPure.HTTPureSpec +SOURCES := $(SRCPATH)/**/* +TESTSOURCES := $(TESTPATH)/**/* +.PHONY: clean test + +test: $(BUILD) $(TESTSOURCES) + $(PULP) test --src-path $(SRCPATH) --build-path $(BUILD) --main $(TESTMAIN) + +$(BUILD): $(COMPONENTS) $(SOURCES) + $(PULP) build --src-path $(SRCPATH) --build-path $(BUILD) + touch $(BUILD) + +$(COMPONENTS): $(BOWERJSON) + $(BOWER) install + +clean: + rm -rf $(OUTPUT) + +$(DOCS): $(COMPONENTS) $(SOURCES) + $(PULP) docs --src-path $(SRCPATH) + rm -rf $(DOCS) + mv generated-docs $(DOCS) + +docs: $(DOCS) +build: $(BUILD) diff --git a/README.md b/README.md new file mode 100644 index 0000000..0956520 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# HTTPure + +A purescript HTTP server framework. + +## Status + +This project is currently an early-stage work in progress. It is not +production-ready yet. You can track what's left before it gets production-ready +by looking at our [roadmap](TODO.md). If you'd like to help us get there +quicker, please contribute! To get started, check +our [contributing guide](CONTRIBUTING.md). + +## Installation + +```bash +bower install purescript-httpure +``` + +## Documentation + +Module documentation is published +on [Pursuit](http://pursuit.purescript.org/packages/purescript-httpure). + +## Quick Start + +TODO + +## Testing + +We have a Makefile that wraps all commands for development. To run the test +suite, in the project root run: + +```bash +make test +``` + +## Contributing + +We are open to accepting contributions! Please see +the [contributing guide](CONTRIBUTING.md). + +## People + +HTTPure is written and maintained +by [Connor Prussin](https://connor.prussin.net). + +We are open to accepting contributions! Please see +the [contributing guide](CONTRIBUTING.md). + +## License + +[MIT](LICENSE) diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..10d0b3f --- /dev/null +++ b/bower.json @@ -0,0 +1,24 @@ +{ + "name": "purescript-httpure", + "homepage": "", + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "" + }, + "ignore": [ + "**/.*", + "output", + "test", + "bower.json" + ], + "dependencies": { + "purescript-prelude": "^3.0.0", + "purescript-node-http": "^4.0.0" + }, + "devDependencies": { + "purescript-psci-support": "^3.0.0", + "purescript-spec": "^1.0.0" + } +} diff --git a/lib/HTTPure.purs b/lib/HTTPure.purs new file mode 100644 index 0000000..b4e5020 --- /dev/null +++ b/lib/HTTPure.purs @@ -0,0 +1,13 @@ +module HTTPure ( + module HTTPure.HTTPureM, + module HTTPure.Server, + module HTTPure.Request, + module HTTPure.Response, + module HTTPure.Route +) where + +import HTTPure.HTTPureM (HTTPureM) +import HTTPure.Server (serve) +import HTTPure.Request (Request, getURL) +import HTTPure.Response (Response, write) +import HTTPure.Route (Method(..), Route) diff --git a/lib/HTTPure/HTTPureM.purs b/lib/HTTPure/HTTPureM.purs new file mode 100644 index 0000000..e5997da --- /dev/null +++ b/lib/HTTPure/HTTPureM.purs @@ -0,0 +1,13 @@ +module HTTPure.HTTPureM + ( HTTPureM + ) where + +import Control.Monad.Eff (Eff) +import Prelude (Unit) +import Node.HTTP (HTTP) + +-- | The `HTTPureM` monad represents actions acting over an HTTPure server +-- | lifecycle. It is the return type of all route handlers and of the `serve` +-- | function. It takes an effects row parameter which enumerates all other +-- | side-effects performed while carrying out the server actions. +type HTTPureM e = Eff (http :: HTTP | e) Unit diff --git a/lib/HTTPure/Request.purs b/lib/HTTPure/Request.purs new file mode 100644 index 0000000..3bf4a66 --- /dev/null +++ b/lib/HTTPure/Request.purs @@ -0,0 +1,25 @@ +module HTTPure.Request + ( Request + , fromHTTPRequest + , getURL + ) where + +import Node.HTTP (HTTP, Request, requestAsStream, requestURL) as HTTP +import Node.Stream (Readable) + +-- | TODO write me +type Request e = { + httpRequest :: HTTP.Request, + stream :: Readable () (http :: HTTP.HTTP | e) +} + +-- | TODO write me +fromHTTPRequest :: forall e. HTTP.Request -> Request e +fromHTTPRequest request = { + httpRequest: request, + stream: HTTP.requestAsStream request +} + +-- | TODO write me +getURL :: forall e. Request e -> String +getURL request = HTTP.requestURL request.httpRequest diff --git a/lib/HTTPure/Response.purs b/lib/HTTPure/Response.purs new file mode 100644 index 0000000..c0974d9 --- /dev/null +++ b/lib/HTTPure/Response.purs @@ -0,0 +1,28 @@ +module HTTPure.Response + ( Response + , fromHTTPResponse + , write + ) where + +import Control.Monad.Eff (Eff) +import Node.Encoding (Encoding(UTF8)) +import Node.HTTP (HTTP, Response, responseAsStream) as HTTP +import Node.Stream (Writable, writeString) +import Prelude (Unit, bind, pure, unit) + +-- | TODO write me +-- | TODO wrap me in a Record so that the HTTP response is accessible +type Response e = Writable () (http :: HTTP.HTTP | e) + +-- | TODO write me +fromHTTPResponse :: forall e. HTTP.Response -> Response e +fromHTTPResponse = HTTP.responseAsStream + +-- | TODO write me +--setStatusCode :: + +-- | TODO write me +write :: forall e. Response e -> String -> Eff (http :: HTTP.HTTP | e) Unit +write response str = do + _ <- writeString response UTF8 str (pure unit) + pure unit diff --git a/lib/HTTPure/Route.purs b/lib/HTTPure/Route.purs new file mode 100644 index 0000000..55e503b --- /dev/null +++ b/lib/HTTPure/Route.purs @@ -0,0 +1,46 @@ +module HTTPure.Route + ( Method(..) + , Route + , RouteHandler + ) where + +import HTTPure.Request (Request) +import HTTPure.Response (Response) +import HTTPure.HTTPureM (HTTPureM) + +-- | The available HTTP methods that a Route can service. +data Method = All | Get | Post | Put | Delete + +-- | All route handler methods - that is, methods for before hooks, after hooks, +-- | or route handlers themselves - have this type signature. +type RouteHandler e = Request e -> Response e -> HTTPureM e + +-- | A Route matches a given HTTP Method against a given URL string. The route +-- | string's format is inspired by express. When a request comes in that +-- | matches the route, the handler is executed against the request and the +-- | response. +type Route e = { + method :: Method, + route :: String, + handler :: RouteHandler e +} + +-- The internal representation of a route. The route is converted from a String +-- to a RouteMatcher, which can cheaply match routes and extract params. +--type LoadedRoute e = { +-- method :: Method, +-- route :: RouteMatcher, +-- handler :: Request e -> Response e -> HTTPure e +--} + +-- The main request handler. + +-- Convert the passed in routes to their internal representation. +--loadRoutes :: forall e. +-- Array (Route e) -> +-- Array (LoadedRoute e) +--loadRoutes = map \route -> { +-- method: route.method, +-- handler: route.handler, +-- route: toRouteMatcher(route.route) +--} diff --git a/lib/HTTPure/Server.purs b/lib/HTTPure/Server.purs new file mode 100644 index 0000000..1a92338 --- /dev/null +++ b/lib/HTTPure/Server.purs @@ -0,0 +1,40 @@ +module HTTPure.Server ( + serve +) where + +import Data.Maybe (Maybe(Nothing)) +import Data.Traversable (traverse_) +import Node.HTTP (Request, Response, ListenOptions, createServer, listen) as HTTP +import Node.Stream (end) +import Prelude (bind, discard, pure, unit, ($), (==)) +import Data.Array (filter) + +import HTTPure.Route (Route) +import HTTPure.Response (fromHTTPResponse) +import HTTPure.Request (fromHTTPRequest, getURL) +import HTTPure.HTTPureM (HTTPureM) + +-- | TODO write me +handleRequest :: forall e. Array (Route e) -> HTTP.Request -> HTTP.Response -> HTTPureM e +handleRequest routes request response = do + traverse_ (\route -> route.handler req resp) (filter matching routes) + end resp (pure unit) + pure unit + where + req = fromHTTPRequest request + resp = fromHTTPResponse response + matching = \route -> route.route == getURL req + +-- | TODO write me +getOptions :: Int -> HTTP.ListenOptions +getOptions port = { + hostname: "localhost", + port: port, + backlog: Nothing +} + +-- | TODO write me +serve :: forall e. Array (Route e) -> Int -> HTTPureM e -> HTTPureM e +serve routes port onStarted = do + server <- HTTP.createServer $ handleRequest routes -- $ loadRoutes routes + HTTP.listen server (getOptions port) onStarted diff --git a/test/HTTPure/HTTPureMSpec.purs b/test/HTTPure/HTTPureMSpec.purs new file mode 100644 index 0000000..9e22c3f --- /dev/null +++ b/test/HTTPure/HTTPureMSpec.purs @@ -0,0 +1,8 @@ +module HTTPure.HTTPureMSpec where + +import Prelude (Unit, pure, unit) +import Test.Spec (Spec) +import Test.Spec.Runner (RunnerEffects) + +httpureMSpec :: Spec (RunnerEffects ()) Unit +httpureMSpec = pure unit diff --git a/test/HTTPure/IntegrationSpec.purs b/test/HTTPure/IntegrationSpec.purs new file mode 100644 index 0000000..530b6f9 --- /dev/null +++ b/test/HTTPure/IntegrationSpec.purs @@ -0,0 +1,13 @@ +module HTTPure.IntegrationSpec where + +import Prelude (Unit, ($)) +import Test.Spec (Spec, describe, pending) +import Test.Spec.Runner (RunnerEffects) + +startsServerSpec :: Spec (RunnerEffects ()) Unit +startsServerSpec = + pending "starts a server" + +integrationSpec :: Spec (RunnerEffects ()) Unit +integrationSpec = describe "integration" $ + startsServerSpec diff --git a/test/HTTPure/RequestSpec.purs b/test/HTTPure/RequestSpec.purs new file mode 100644 index 0000000..499afce --- /dev/null +++ b/test/HTTPure/RequestSpec.purs @@ -0,0 +1,18 @@ +module HTTPure.RequestSpec where + +import Prelude (Unit, discard, ($)) +import Test.Spec (Spec, describe, pending) +import Test.Spec.Runner (RunnerEffects) + +fromHTTPRequestSpec :: Spec (RunnerEffects ()) Unit +fromHTTPRequestSpec = describe "fromHTTPRequest" $ + pending "wraps an HTTP request" + +getURLSpec :: Spec (RunnerEffects ()) Unit +getURLSpec = describe "getURL" $ + pending "returns the URL of the request" + +requestSpec :: Spec (RunnerEffects ()) Unit +requestSpec = describe "Request" do + fromHTTPRequestSpec + getURLSpec diff --git a/test/HTTPure/ResponseSpec.purs b/test/HTTPure/ResponseSpec.purs new file mode 100644 index 0000000..d70d538 --- /dev/null +++ b/test/HTTPure/ResponseSpec.purs @@ -0,0 +1,23 @@ +module HTTPure.ResponseSpec where + +import Prelude (Unit, discard, ($)) +import Test.Spec (Spec, describe, pending) +import Test.Spec.Runner (RunnerEffects) + +fromHTTPResponseSpec :: Spec (RunnerEffects ()) Unit +fromHTTPResponseSpec = describe "fromHTTPResponse" $ + pending "wraps an HTTP response" + +setStatusCodeSpec :: Spec (RunnerEffects ()) Unit +setStatusCodeSpec = describe "setStatusCode" $ + pending "sets the status code" + +writeSpec :: Spec (RunnerEffects ()) Unit +writeSpec = describe "write" $ + pending "adds the string to the response output" + +responseSpec :: Spec (RunnerEffects ()) Unit +responseSpec = describe "Response" do + fromHTTPResponseSpec + setStatusCodeSpec + writeSpec diff --git a/test/HTTPure/RouteSpec.purs b/test/HTTPure/RouteSpec.purs new file mode 100644 index 0000000..b834d1b --- /dev/null +++ b/test/HTTPure/RouteSpec.purs @@ -0,0 +1,8 @@ +module HTTPure.RouteSpec where + +import Prelude (Unit, pure, unit) +import Test.Spec (Spec) +import Test.Spec.Runner (RunnerEffects) + +routeSpec :: Spec (RunnerEffects ()) Unit +routeSpec = pure unit diff --git a/test/HTTPure/ServerSpec.purs b/test/HTTPure/ServerSpec.purs new file mode 100644 index 0000000..ec5aaba --- /dev/null +++ b/test/HTTPure/ServerSpec.purs @@ -0,0 +1,23 @@ +module HTTPure.ServerSpec where + +import Prelude (Unit, discard, ($)) +import Test.Spec (Spec, describe, pending) +import Test.Spec.Runner (RunnerEffects) + +handleRequestSpec :: Spec (RunnerEffects ()) Unit +handleRequestSpec = describe "handleRequest" $ + pending "handles the request" + +getOptionsSpec :: Spec (RunnerEffects ()) Unit +getOptionsSpec = describe "getOptions" $ + pending "returns an options object" + +serveSpec :: Spec (RunnerEffects ()) Unit +serveSpec = describe "serve" $ + pending "starts the server" + +serverSpec :: Spec (RunnerEffects ()) Unit +serverSpec = describe "Server" do + handleRequestSpec + getOptionsSpec + serveSpec diff --git a/test/HTTPureSpec.purs b/test/HTTPureSpec.purs new file mode 100644 index 0000000..a20f7aa --- /dev/null +++ b/test/HTTPureSpec.purs @@ -0,0 +1,23 @@ +module HTTPure.HTTPureSpec where + +import Prelude (Unit, discard, ($)) +import Control.Monad.Eff (Eff) +import Test.Spec (describe) +import Test.Spec.Reporter (specReporter) +import Test.Spec.Runner (RunnerEffects, run) + +import HTTPure.HTTPureMSpec (httpureMSpec) +import HTTPure.RequestSpec (requestSpec) +import HTTPure.ResponseSpec (responseSpec) +import HTTPure.RouteSpec (routeSpec) +import HTTPure.ServerSpec (serverSpec) +import HTTPure.IntegrationSpec (integrationSpec) + +main :: Eff (RunnerEffects ()) Unit +main = run [ specReporter ] $ describe "HTTPure" do + httpureMSpec + requestSpec + responseSpec + routeSpec + serverSpec + integrationSpec