nginx-haskell-module

作者: Lupino | 来源:发表于2018-10-03 13:34 被阅读39次

    nginx-haskell-module 是一个 nginx 的扩展模块,有了它我们就可以用 haskell 来写 nginx 的配置文件,甚至做一些很高级的应用。我们现在先把他的例子跑起来看看。

    编译 nginx

    首先下载 nginx 源码 ,然后解压:

    wget https://nginx.org/download/nginx-1.15.5.tar.gz
    tar xvf nginx-1.15.5.tar.gz
    cd nginx-1.15.5
    

    下载模块 echo-nginx-modulenginx-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 模块

    使用 stack 来编译 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=%5Ei'              # URL-encoded ^i
    matches (intelligence, ^i) = 1
    $ curl 'http://localhost:8010/?d=intelligence&a=%5EI'              # 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=%5B1%2C2%2C3%5D'                  # URL-encoded [1,2,3]
    isJSONListOfInts ([1,2,3]) = 1
    $ curl 'http://localhost:8010/?m=unknown'
    isJSONListOfInts (unknown) = 0
    $ curl 'http://localhost:8010/?n=%5B10%2C20%2C30%2C40%5D&take=3'   # URL-encoded [10,20,30,40]
    jSONListOfIntsTakeN ([10,20,30,40], 3) = [10,20,30]
    $ curl 'http://localhost:8010/?n=%5B10%2C20%2C30%2C40%5D&take=undefined'
    jSONListOfIntsTakeN ([10,20,30,40], undefined) = []
    $ curl -D- 'http://localhost:8010/content?n=%5B10%2C20%2C30%2C40%5D&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>
    

    结语

    到这儿我们已经把模块基本的东西给跑起来了。这只是个开始,有了他我们可以做很多事情,具体可以看看 README

    相关文章

      网友评论

        本文标题:nginx-haskell-module

        本文链接:https://www.haomeiwen.com/subject/tpuzoftx.html