ngx-export-tools-extra 0.2.2.1 → 0.3.0.0
raw patch · 5 files changed
+274/−14 lines, 5 filesdep +case-insensitivePVP ok
version bump matches the API change (PVP)
Dependencies added: case-insensitive
API changes (from Hackage documentation)
+ NgxExport.Tools.Subrequest: instance Data.Aeson.Types.FromJSON.FromJSON NgxExport.Tools.Subrequest.SubrequestConf
+ NgxExport.Tools.Subrequest: instance GHC.Exception.Type.Exception NgxExport.Tools.Subrequest.SubrequestParseError
+ NgxExport.Tools.Subrequest: instance GHC.Show.Show NgxExport.Tools.Subrequest.SubrequestParseError
+ NgxExport.Tools.Subrequest: subrequest :: ByteString -> IO ByteString
Files
- Changelog.md +5/−0
- NgxExport/Tools/Aggregate.hs +2/−1
- NgxExport/Tools/EDE.hs +9/−10
- NgxExport/Tools/Subrequest.hs +253/−0
- ngx-export-tools-extra.cabal +5/−3
Changelog.md view
@@ -1,3 +1,8 @@+### 0.3.0.0++- Added module *NgxExport.Tools.Subrequest* for making HTTP (sub)requests+ easily.+ ### 0.2.2.0 - Added Cabal flag *EDE* to detect automatically if EDE module can be built.
NgxExport/Tools/Aggregate.hs view
@@ -76,7 +76,7 @@ -- {-\# LANGUAGE TemplateHaskell, DeriveGeneric, TypeApplications \#-} -- {-\# LANGUAGE OverloadedStrings, BangPatterns \#-} ----- module TestToolsExtra where+-- module TestToolsExtraAggregate where -- -- import NgxExport -- import NgxExport.Tools@@ -421,6 +421,7 @@ ,ngxExportSimpleServiceTyped fName ''AggregateServerConf SingleShotService ]+ -- | Reports data to an aggregate service. -- -- If reported data is 'Nothing' then data collected on the aggregate service
NgxExport/Tools/EDE.hs view
@@ -85,15 +85,14 @@ -- 'ngxExportIOYY' \'renderEDETemplateFromFreeValue -- -- urlDecode :: ByteString -> L.ByteString--- __/urlDecode/__ = L.fromStrict . URI.urlDecode False+-- urlDecode = L.fromStrict . URI.urlDecode False -- -- 'ngxExportYY' \'urlDecode -- @ ----- Besides the two exporters imported from the EDE module, two additional--- exporters were defined here: /renderEDETemplateFromFreeValue/ and--- /urlDecode/. We are going to use them for parsing JSON values from HTTP--- cookies.+-- Besides the two handlers imported from the EDE module, two additional+-- handlers were defined here: /renderEDETemplateFromFreeValue/ and /urlDecode/.+-- We are going to use them for parsing JSON values from HTTP cookies. -- -- ==== File /nginx.conf/ -- @@@ -144,9 +143,9 @@ -- } -- -- location \/cookie {--- haskell_run __/urlDecode/__ $hs_cookie_user $cookie_user;+-- haskell_run urlDecode $hs_cookie_user $cookie_user; -- haskell_run __/renderEDETemplateFromFreeValue/__ $hs_user_from_cookie--- user|$hs_cookie_user;+-- __/user/__|$hs_cookie_user; -- rewrite ^ \/internal\/user\/$hs_user_from_cookie last; -- } -- }@@ -240,9 +239,9 @@ -- | Renders an EDE template from a JSON object. ----- This is the core function of the /renderEDETemplate/ exporter. Accepts a--- JSON object written in a 'L.ByteString' and a key to find a compiled EDE--- template declared by the /compileEDETemplates/ exporter.+-- This is the core function of the /renderEDETemplate/ handler. Accepts a JSON+-- object written in a 'L.ByteString' and a key to find a compiled EDE template+-- declared by the /compileEDETemplates/ handler. renderEDETemplate :: L.ByteString -- ^ JSON object -> ByteString -- ^ Key to find the EDE template -> IO L.ByteString
+ NgxExport/Tools/Subrequest.hs view
@@ -0,0 +1,253 @@+{-# LANGUAGE TemplateHaskell, OverloadedStrings, RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}++-----------------------------------------------------------------------------+-- |+-- Module : NgxExport.Tools.Subrequest+-- Copyright : (c) Alexey Radkov 2020+-- License : BSD-style+--+-- Maintainer : alexey.radkov@gmail.com+-- Stability : experimental+-- Portability : non-portable (requires Template Haskell)+--+-- Easy HTTP subrequests from the more extra tools collection for+-- <http://github.com/lyokha/nginx-haskell-module nginx-haskell-module>.+--+-----------------------------------------------------------------------------+++module NgxExport.Tools.Subrequest (+ -- * Making HTTP subrequests+ -- $makingHTTPSubrequests+ subrequest+ ) where++import NgxExport+import NgxExport.Tools++import Network.HTTP.Client+import Data.ByteString (ByteString)+import qualified Data.ByteString.Lazy as L+import qualified Data.Text as T+import qualified Data.Text.Encoding as T+import Data.Text (Text)+import Data.CaseInsensitive (mk)+import Data.Aeson+import Control.Arrow+import Control.Exception+import System.IO.Unsafe++-- $makingHTTPSubrequests+--+-- Using asynchronous variable handlers and services together with the HTTP+-- client from "Network.HTTP.Client" allows making HTTP subrequests easily.+-- This module provides such functionality by exporting asynchronous variable+-- handler __/subrequest/__ and function 'subrequest' to build custom handlers.+--+-- Below is a simple example.+--+-- ==== File /test_tools_extra_subrequest.hs/+-- @+-- {-\# LANGUAGE TemplateHaskell \#-}+--+-- module TestToolsExtraSubrequest where+--+-- import NgxExport+-- import NgxExport.Tools+-- import NgxExport.Tools.Subrequest+--+-- import Data.ByteString (ByteString)+-- import qualified Data.ByteString.Lazy as L+--+-- subrequestFromService :: ByteString -> Bool -> IO L.ByteString+-- __/subrequestFromService/__ = const . 'subrequest'+--+-- 'ngxExportSimpleService' \'subrequestFromService $+-- 'PersistentService' $ Just $ 'Sec' 10+-- @+--+-- Handler /subrequestFromService/ will be used in a /periodical/ service which+-- will retrieve data from a specified URI every 10 seconds.+--+-- ==== File /nginx.conf/+-- @+-- user nobody;+-- worker_processes 2;+--+-- events {+-- worker_connections 1024;+-- }+--+-- http {+-- default_type application\/octet-stream;+-- sendfile on;+--+-- haskell load \/var\/lib\/nginx\/test_tools_extra_subrequest.so;+--+-- upstream backend {+-- server 127.0.0.1:8020;+-- }+--+-- haskell_run_service __/simpleService_subrequestFromService/__ $hs_service_httpbin+-- \'{\"uri\": \"http:\/\/httpbin.org\"}\';+--+-- haskell_var_empty_on_error $hs_subrequest;+--+-- server {+-- listen 8010;+-- server_name main;+-- error_log \/tmp\/nginx-test-haskell-error.log;+-- access_log \/tmp\/nginx-test-haskell-access.log;+--+-- location \/ {+-- haskell_run_async __/subrequest/__ $hs_subrequest+-- \'{\"uri\": \"http:\/\/127.0.0.1:8020\/proxy\",+-- \"headers\": [[\"Custom-Header\", \"$arg_a\"]]}\';+--+-- if ($hs_subrequest = \'\') {+-- echo_status 500;+-- echo \"Failed to perform subrequest\";+-- }+--+-- echo -n $hs_subrequest;+-- }+--+-- location \/proxy {+-- internal;+-- proxy_pass http:\/\/backend;+-- }+--+-- location \/httpbin {+-- echo $hs_service_httpbin;+-- }+-- }+--+-- server {+-- listen 8020;+-- server_name backend;+--+-- location\ / {+-- set $custom_header $http_custom_header;+-- echo \"In backend, Custom-Header is \'$custom_header\'\";+-- }+-- }+-- }+-- @+--+-- Configurations of subrequests are defined via JSON objects which contain URI+-- and other relevant data such as HTTP method, request body and headers. In+-- this configuration we are running a periodical service which gets contents of+-- /httpbin.org/ every 10 seconds, and doing a subrequest to a virtual server+-- /backend/ on every request to /location \//. In this subrequest, an HTTP+-- header /Custom-Header/ is sent to the backend with value equal to the value+-- of argument /a/ from the client request's URI.+--+-- It is worth noting that making HTTP subrequests to the own Nginx service+-- (e.g. via /127.0.0.1/) allows for leveraging well-known advantages of Nginx+-- such as load-balancing via upstreams as it is happening in this example.+--+-- ==== A simple test+--+-- > $ curl -s 'http://localhost:8010/httpbin' | head+-- > <!DOCTYPE html>+-- > <html lang="en">+-- >+-- > <head>+-- > <meta charset="UTF-8">+-- > <title>httpbin.org</title>+-- > <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700"+-- > rel="stylesheet">+-- > <link rel="stylesheet" type="text/css" href="/flasgger_static/swagger-ui.css">+-- > <link rel="icon" type="image/png" href="/static/favicon.ico" sizes="64x64 32x32 16x16" />+--+-- > $ curl 'http://localhost:8010/?a=Value'+-- > In backend, Custom-Header is 'Value'+--+-- Let's do a nasty thing. By injecting a comma into the argument /a/ we shall+-- break parsing JSON.+--+-- > $ curl -D- 'http://localhost:8010/?a=Value"'+-- > HTTP/1.1 500 Internal Server Error+-- > Server: nginx/1.17.9+-- > Date: Mon, 30 Mar 2020 14:42:42 GMT+-- > Content-Type: application/octet-stream+-- > Transfer-Encoding: chunked+-- > Connection: keep-alive+-- >+-- > Failed to perform subrequest++type ReqHeaders = [(Text, Text)]++data SubrequestParseError = SubrequestParseError deriving Show+instance Exception SubrequestParseError++data SubrequestConf =+ SubrequestConf { srMethod :: Text+ , srUri :: String+ , srBody :: Text+ , srHeaders :: ReqHeaders+ }+instance FromJSON SubrequestConf where+ parseJSON = withObject "SubrequestConf" $ \o -> do+ srMethod <- o .:? "method" .!= "GET"+ srUri <- o .: "uri"+ srBody <- o .:? "body" .!= ""+ srHeaders <- o .:? "headers" .!= []+ return SubrequestConf {..}++doSubrequest :: SubrequestConf -> IO L.ByteString+doSubrequest SubrequestConf {..} = do+ req <- parseUrlThrow srUri+ let req' = if T.null srMethod+ then req+ else req { method = T.encodeUtf8 srMethod }+ req'' = if T.null srBody+ then req'+ else req' { requestBody =+ RequestBodyBS $ T.encodeUtf8 srBody }+ req''' = if null srHeaders+ then req''+ -- TODO: implement caching of frequent header values+ else req'' { requestHeaders =+ map (mk . T.encodeUtf8 *** T.encodeUtf8)+ srHeaders }+ responseBody <$> httpLbs req''' httpManager++httpManager :: Manager+httpManager = unsafePerformIO $ newManager defaultManagerSettings+{-# NOINLINE httpManager #-}++-- | Makes an HTTP request.+--+-- This is the core function of the /subrequest/ handler. From perspective of+-- an Nginx request, it spawns a subrequest, hence the name. However, this+-- function can also be used to initiate an original HTTP request from a+-- service handler.+--+-- Accepts a JSON object representing an opaque type /SubrequestConf/.+-- The object may contain 4 fields: /method/ (optional, default is /GET/),+-- /uri/ (mandatory), /body/ (optional, default is an empty value), and+-- /headers/ (optional, default is an empty array).+--+-- Examples of subrequest configurations:+--+-- > {"uri": "http://example.com/"}+--+-- > {"uri": "http://127.0.0.1/subreq", "method": "POST", "body": "some value"}+--+-- > {"uri": "http://127.0.0.1/subreq",+-- > "headers": [["Header1", "Value1"], ["Header2", "Value2"]]}+--+-- Returns the response body if HTTP status of the response is /2xx/, otherwise+-- throws an error. To avoid leakage of error messages into variable handlers,+-- put the corresponding variables into the list of the directive+-- /haskell_var_empty_on_error/.+subrequest+ :: ByteString -- ^ Subrequest configuration+ -> IO L.ByteString+subrequest = maybe (throwIO SubrequestParseError) doSubrequest .+ readFromByteStringAsJSON @SubrequestConf++ngxExportAsyncIOYY 'subrequest+
ngx-export-tools-extra.cabal view
@@ -1,5 +1,5 @@ name: ngx-export-tools-extra-version: 0.2.2.1+version: 0.3.0.0 synopsis: More extra tools for Nginx haskell module description: More extra tools for <https://github.com/lyokha/nginx-haskell-module Nginx haskell module>.@@ -16,8 +16,8 @@ cabal-version: >= 1.8 source-repository head- Type: git- Location: https://github.com/lyokha/ngx-export-tools-extra+ type: git+ location: https://github.com/lyokha/ngx-export-tools-extra flag EDE description: Build EDE module.@@ -38,6 +38,7 @@ , aeson >= 1.0.0.0 , http-types >= 0.7.0 , http-client+ , case-insensitive , containers , unordered-containers , enclosed-exceptions@@ -58,6 +59,7 @@ , trifecta <= 2 exposed-modules: NgxExport.Tools.Aggregate+ , NgxExport.Tools.Subrequest if flag(EDE) exposed-modules: NgxExport.Tools.EDE