https://GitHub.com/lyokha/nginx-haskell-module 是一个 https://nginx.org/ 的扩展模块, 有了它我们就可以用 https://www.haskell.org 来写 nginx 的配置文件, 甚至做一些很高级的应用. 我们现在先把他的例子跑起来看看.
首先下载 nginx 源码 https://nginx.org/download/nginx-1.15.5.tar.gz , 然后解压:
- wget https://nginx.org/download/nginx-1.15.5.tar.gz
- tar xvf nginx-1.15.5.tar.gz
- cd nginx-1.15.5
下载模块 https://GitHub.com/openresty/echo-nginx-module 和 https://GitHub.com/lyokha/nginx-haskell-module
- mkdir modules
- cd modules
- Git clone https://GitHub.com/openresty/echo-nginx-module.Git
- Git clone https://GitHub.com/lyokha/nginx-haskell-module.Git
- cd ..
然后编译
- ./configure --prefix=/root/nginx \
- --add-module=modules/nginx-haskell-module \
- --add-module=modules/echo-nginx-module \
- --with-http_ssl_module
- make install
编译 Haskell 模块
使用 https://docs.haskellstack.org/en/stable/README/ 来编译 haskell 模块
- cd modules/nginx-haskell-module/haskell/ngx-export
- stack init
- stack build
写一个 haskell 模块
- mkdir test
- cd test
- VIM haskell.hs
内容如下:
- {-# LANGUAGE FlexibleInstances #-}
- {-# LANGUAGE MagicHash #-}
- {-# LANGUAGE TemplateHaskell #-}
- {-# LANGUAGE TupleSections #-}
- {-# LANGUAGE ViewPatterns #-}
- module NgxHaskellUserRuntime where
- import Control.Exception
- import Control.Monad
- import Data.Aeson
- import qualified Data.ByteString as B
- import qualified Data.ByteString.Char8 as C8
- import Data.ByteString.Internal (accursedUnutterablePerformIO)
- import qualified Data.ByteString.Lazy as L
- import qualified Data.ByteString.Lazy.Char8 as C8L
- import Data.ByteString.Unsafe
- import qualified Data.Char as C
- import Data.Function (on)
- import Data.Maybe
- import qualified Data.Text.Encoding as T
- import NgxExport
- import Safe
- import Text.Pandoc
- import Text.Pandoc.Builder
- import Text.Regex.PCRE
- toUpper = map C.toUpper
- ngxExportSS 'toUpper
- takeN = take . readDef 0
- ngxExportSSS 'takeN
- ngxExportSS 'reverse
- class UrlDecodable a
- where doURLDecode :: a -> Maybe a
- instance UrlDecodable String where
- -- adopted from
- -- http://www.rosettacode.org/wiki/URL_decoding#Haskell
- doURLDecode [] = Just []
- doURLDecode ('%' : xs) =
- case xs of
- (a : b : xss) ->
- (:) . C.chr <$> readMay ('0' : 'x' : [a, b])
- <*> doURLDecode xss
- _ -> Nothing
- doURLDecode ('+' : xs) = (' ' :) <$> doURLDecode xs
- doURLDecode (x : xs) = (x :) <$> doURLDecode xs
- instance UrlDecodable L.ByteString where
- -- adopted for ByteString arguments from
- -- http://www.rosettacode.org/wiki/URL_decoding#Haskell
- doURLDecode (L.null -> True) = Just L.empty
- doURLDecode (L.uncons -> Just (37, xs))
- | L.length xs> 1 =
- let (C8L.unpack -> c, xss) = L.splitAt 2 xs
- in L.cons <$> readMay ('0' : 'x' : c)
- <*> doURLDecode xss
- | otherwise = Nothing
- doURLDecode (L.uncons -> Just (43, xs)) = (32 `L.cons`) <$> doURLDecode xs
- doURLDecode (L.uncons -> Just (x, xs)) = (x `L.cons`) <$> doURLDecode xs
- -- does not match when any of the 2 args is empty or not decodable
- matches = (fromMaybe False .) . liftM2 (=~) `on` (doURLDecode =<<) . toMaybe
- where toMaybe [] = Nothing
- toMaybe a = Just a
- ngxExportBSS 'matches
- firstNotEmpty = headDef "" . filter (not . null)
- ngxExportSLS 'firstNotEmpty
- isInList [] = False
- isInList (x : xs) = x `elem` xs
- ngxExportBLS 'isInList
- jSONListOfInts :: B.ByteString -> Maybe [Int]
- jSONListOfInts = (decode =<<) . doURLDecode . L.fromStrict
- isJSONListOfInts = isJust . jSONListOfInts
- ngxExportBY 'isJSONListOfInts
- jSONListOfIntsTakeN x = encode $ maybe [] (take n) $ jSONListOfInts y
- where (readDef 0 . C8.unpack -> n, B.tail -> y) = B.break (== 124) x
- ngxExportYY 'jSONListOfIntsTakeN
- urlDecode = fromMaybe "" . doURLDecode
- ngxExportSS 'urlDecode
- -- compatible with Pandoc 2.0 (will not compile for older versions)
- fromMd (T.decodeUtf8 -> x) = uncurry (, packLiteral 9 "text/html"#, ) $
- case runPure $ readMarkdown def x>>= writeHtml of
- Right p -> (fromText p, 200)
- Left (displayException -> e) -> (case runPure $ writeError e of
- Right p -> fromText p
- Left _ -> C8L.pack e, 500)
- where packLiteral l s =
- accursedUnutterablePerformIO $ unsafePackAddressLen l s
- fromText = C8L.fromStrict . T.encodeUtf8
- writeHtml = writeHtml5String defHtmlWriterOptions
- writeError = writeHtml . doc . para . singleton . Str
- defHtmlWriterOptions = def
- { writerTemplate = Just "<HTML>\\n<body>\\n$body$</body></HTML>" }
- ngxExportHandler 'fromMd
- toYesNo "0" = "No"
- toYesNo "1" = "Yes"
- toYesNo _ = "Unknown"
- ngxExportSS 'toYesNo
安装一下相关的依赖:
stack install pandoc
然后编译:
- stack ghc -- -O2 -dynamic -shared -fPIC -L$(ghc --print-libdir)/rts -lHSrts_thr-ghc$(ghc --numeric-version) haskell.hs -o test.so
- mkdir -p /root/nginx/modules
- cp test.so /root/nginx/modules/
配置运行
VIM /root/nginx/conf/nginx.conf
内容如下:
- user nobody;
- worker_processes 2;
- events {
- worker_connections 1024;
- }
- http {
- default_type application/octet-stream;
- sendfile on;
- haskell load /root/nginx/modules/test.so;
- 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 toUpper $hs_a $arg_a;
- echo "toUpper ($arg_a) = $hs_a";
- if ($arg_b) {
- haskell_run takeN $hs_a $arg_b $arg_a;
- echo "takeN ($arg_a, $arg_b) = $hs_a";
- break;
- }
- if ($arg_c) {
- haskell_run reverse $hs_a $arg_c;
- echo "reverse ($arg_c) = $hs_a";
- break;
- }
- if ($arg_d) {
- haskell_run matches $hs_a $arg_d $arg_a;
- haskell_run urlDecode $hs_b $arg_a;
- echo "matches ($arg_d, $hs_b) = $hs_a";
- break;
- }
- if ($arg_e) {
- haskell_run firstNotEmpty $hs_a $arg_f $arg_g $arg_a;
- echo "firstNotEmpty ($arg_f, $arg_g, $arg_a) = $hs_a";
- break;
- }
- if ($arg_l) {
- haskell_run isInList $hs_a $arg_a secret1 secret2 secret3;
- echo "isInList ($arg_a, <secret words>) = $hs_a";
- break;
- }
- if ($arg_m) {
- haskell_run isJSONListOfInts $hs_a $arg_m;
- haskell_run urlDecode $hs_b $arg_m;
- echo "isJSONListOfInts ($hs_b) = $hs_a";
- break;
- }
- if ($arg_n) {
- haskell_run jSONListOfIntsTakeN $hs_a $arg_take|$arg_n;
- haskell_run urlDecode $hs_b $arg_n;
- echo "jSONListOfIntsTakeN ($hs_b, $arg_take) = $hs_a";
- break;
- }
- }
- location /content {
- haskell_run isJSONListOfInts $hs_a $arg_n;
- haskell_run toYesNo $hs_b $hs_a;
- haskell_run jSONListOfIntsTakeN $hs_c $arg_take|$arg_n;
- haskell_run urlDecode $hs_d $arg_n;
- haskell_content fromMd "
- ## Do some JSON parsing
- ### Given ``$hs_d``
- * Is this list of integer numbers?
- + *$hs_b*
- * Take $arg_take elements
- + *``$hs_c``*
- ";
- }
- }
- }
测试
- $ curl 'http://localhost:8010/?a=hello_world'
- toUpper (hello_world) = HELLO_WORLD
- $ curl 'http://localhost:8010/?a=hello_world&b=4'
- takeN (hello_world, 4) = hell
- $ curl 'http://localhost:8010/?a=hello_world&b=oops'
- takeN (hello_world, oops) =
- $ curl 'http://localhost:8010/?c=intelligence'
- reverse (intelligence) = ecnegilletni
- $ curl 'http://localhost:8010/?d=intelligence&a=^i' # URL-encoded ^i
- matches (intelligence, ^i) = 1
- $ curl 'http://localhost:8010/?d=intelligence&a=^I' # URL-encoded ^I
- matches (intelligence, ^I) = 0
- $ curl 'http://localhost:8010/?e=1&g=intelligence&a=smart'
- firstNotEmpty (, intelligence, smart) = intelligence
- $ curl 'http://localhost:8010/?e=1&g=intelligence&f=smart'
- firstNotEmpty (smart, intelligence, ) = smart
- $ curl 'http://localhost:8010/?e=1'
- firstNotEmpty (, , ) =
- $ curl 'http://localhost:8010/?l=1'
- isInList (, <secret words>) = 0
- $ curl 'http://localhost:8010/?l=1&a=s'
- isInList (s, <secret words>) = 0
- $ curl 'http://localhost:8010/?l=1&a=secret2'
- isInList (secret2, <secret words>) = 1
- $ curl 'http://localhost:8010/?m=[1,2,3]' # URL-encoded [1,2,3]
- isJSONListOfInts ([1,2,3]) = 1
- $ curl 'http://localhost:8010/?m=unknown'
- isJSONListOfInts (unknown) = 0
- $ curl 'http://localhost:8010/?n=[10,20,30,40]&take=3' # URL-encoded [10,20,30,40]
- jSONListOfIntsTakeN ([10,20,30,40], 3) = [10,20,30]
- $ curl 'http://localhost:8010/?n=[10,20,30,40]&take=undefined'
- jSONListOfIntsTakeN ([10,20,30,40], undefined) = []
- $ curl -D- 'http://localhost:8010/content?n=[10,20,30,40]&take=3'
- HTTP/1.1 200 OK
- Server: nginx/1.8.0
- Date: Fri, 04 Mar 2016 15:17:44 GMT
- Content-Type: text/HTML
- Content-Length: 323
- Connection: keep-alive
- <HTML>
- <body>
- <h2 id="do-some-JSON-parsing">Do some JSON parsing</h2>
- <h3 id="given-10203040">Given <code>[10,20,30,40]</code></h3>
- <ul>
- <li><p>Is this list of integer numbers?</p>
- <ul>
- <li><em>Yes</em></li>
- </ul></li>
- <li><p>Take 3 elements</p>
- <ul>
- <li><em><code>[10,20,30]</code></em></li>
- </ul></li>
- </ul></body></HTML>
结语
到这儿我们已经把模块基本的东西给跑起来了. 这只是个开始, 有了他我们可以做很多事情, 具体可以看看 .
来源: https://juejin.im/entry/5bb45fa55188255c6140e01b