<commit_before>#!/usr/bin/env luvit
--[[ src/lua/dnsresolvd.lua
 * ============================================================================
 * DNS Resolver Daemon (dnsresolvd). Version 0.1
 * ============================================================================
 * A daemon that performs DNS lookups for the given hostname
 * passed in an HTTP request, with the focus on its implementation
 * using various programming languages. (Luvit-boosted impl.)
 * ============================================================================
 * Copyright (C) 2017 <NAME>) Golubtsov
 *
 * (See the LICENSE file at the top of the source tree.)
--]]

local path = require("path")
local http = require("http")
local url  = require("url")
local dns  = require("dns")

local pp   = require("pretty-print")

local aux  = require("dnsresolvh")

--[[
 * Performs DNS lookup action for the given hostname,
 * i.e. (in this case) IP address retrieval by hostname.
 *
 * It first prepares and runs the server instance, then does all the rest.
 *
 * @param _ret        The status code passed in from the caller.
 * @param port_number The server port number to listen on.
 * @param daemon_name The daemon name (executable/script name).
 *
 * @return The status code indicating the daemon overall execution outcome.
--]]
local dns_lookup = function(_ret, port_number, daemon_name)
    local ret = _ret

    --[[
     * Creating, configuring, and starting the server.
     *
     * @param req  The HTTP request  object.
     * @param resp The HTTP response object.
     *
     * @return The HTTP server object.
    --]]
    local daemon = http.createServer(function(req, resp)
        -- Parsing and validating query params.
        local query = url.parse(req.url, true).query

        -- http://localhost:<port_number>/?h=<hostname>
        --                                 |
        local hostname = query.h -- <------+

        if (hostname == nil) then
            hostname = aux._DEF_HOSTNAME
        end

        --[[
         * Writes the response with the DNS record retrieved.
         *
         * @param hostname The effective hostname to look up for.
         * @param e        The Error object (if any error occurs).
         * @param rec      The DNS record retrieved.
        --]]
        local resp_write = function(hostname, e, rec)
            local resp_buffer = "<!DOCTYPE html>"                                                                               .. aux._NEW_LINE
.. "<html lang=\"en-US\" dir=\"ltr\">" .. aux._NEW_LINE .. "<head>"                                                             .. aux._NEW_LINE
.. "<meta http-equiv=\"Content-Type\"    content=\"" .. aux._HDR_CONTENT_TYPE .. "\" />"                                        .. aux._NEW_LINE
.. "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />"                                                       .. aux._NEW_LINE
.. "<!-- No caching at all for:                                                                       -->"                      .. aux._NEW_LINE
.. "<meta http-equiv=\"Cache-Control\"   content=\"" .. aux._HDR_CACHE_CONTROL .. "\" /> <!-- HTTP/1.1 -->"                     .. aux._NEW_LINE
.. "<meta http-equiv=\"Expires\"         content=\"" .. aux._HDR_EXPIRES .. "\"       /> <!-- Proxies  -->"                     .. aux._NEW_LINE
.. "<meta http-equiv=\"Pragma\"          content=\"" .. aux._HDR_PRAGMA .. "\"                            /> <!-- HTTP/1.0 -->" .. aux._NEW_LINE
.. "<meta       name=\"viewport\"        content=\"width=device-width,initial-scale=1\" />"                                     .. aux._NEW_LINE
.. "<meta       name=\"description\"     content=\"" .. aux._DMN_DESCRIPTION .. "\" />"                                         .. aux._NEW_LINE
.. "<title>" .. aux._DMN_NAME .. "</title>" .. aux._NEW_LINE .. "</head>"                                                       .. aux._NEW_LINE
.. "<body id=\"dnsresolvd\">"             .. aux._NEW_LINE .. "<p>"
.. hostname .. " ==&gt; "

            if (e ~= nil) then
                resp_buffer = resp_buffer .. aux._ERR_PREFIX
                                          .. aux._COLON_SPACE_SEP
                                          .. aux._ERR_COULD_NOT_LOOKUP
            else
                pp.prettyPrint(rec)

                if (#rec == 0) then
                    ret = aux._EXIT_FAILURE

                    return ret
                else
                    local addr = rec[1].address --> The IP address for host.
                    local ver  = rec[1].type    --> The IP version (family).

                        if (ver == dns.TYPE_A   ) then
                        ver = 4
                    elseif (ver == dns.TYPE_AAAA) then
                        ver = 6
                    end

                    resp_buffer = resp_buffer .. addr .. " (IPv" .. ver .. ")"
                end
            end

            resp_buffer = resp_buffer .. "</p>"    .. aux._NEW_LINE
                                      .. "</body>" .. aux._NEW_LINE
                                      .. "</html>" .. aux._NEW_LINE

            -- Adding headers to the response.
            resp:writeHead(aux._RSC_HTTP_200_OK, {
                ["Content-Type"]  = aux._HDR_CONTENT_TYPE,
                ["Cache-Control"] = aux._HDR_CACHE_CONTROL,
                ["Expires"]       = aux._HDR_EXPIRES,
                ["Pragma"]        = aux._HDR_PRAGMA
            })

            -- Writing the response out.
            resp:write(resp_buffer)

            -- Closing the response stream.
            resp:finish()
        end

        --[[
         * Performing DNS lookup for the given hostname
         * and writing the response out.
         *
         * Since Luvit actually doesn't have the dns.lookup() method,
         * it needs to perform a so-called two-stage lookup operation:
         *
         *     If the host doesn't have the A record (IPv4),
         *     trying to find its AAAA record (IPv6).
        --]]
        dns.resolve4        (hostname, function(e, rec)
            ret = resp_write(hostname,          e, rec)

            if (ret == aux._EXIT_FAILURE) then
                dns.resolve6  (hostname, function(e, rec)
                    resp_write(hostname,          e, rec)
                end)
            end
        end)
    end):listen(port_number)

    pp.prettyPrint(daemon)

    -- FIXME: Ported one-to-one from Node.js impl. Not working.
    daemon:on(aux._EVE_ERROR, function(e)
        ret = aux._EXIT_FAILURE

        if (e.code == aux._ERR_EADDRINUSE) then
            print(daemon_name .. aux._ERR_CANNOT_START_SERVER
                              .. aux._ERR_SRV_PORT_IS_IN_USE
                              .. aux._NEW_LINE)
        else
            print(daemon_name .. aux._ERR_CANNOT_START_SERVER
                              .. aux._ERR_SRV_UNKNOWN_REASON
                              .. aux._NEW_LINE)
        end

        _cleanups_fixate()

        return ret
    end)

    -- FIXME: Ported one-to-one from Node.js impl. Not working.
    daemon:on(aux._EVE_LISTENING, function(e)
        print(aux._MSG_SERVER_STARTED_1 .. port_number .. aux._NEW_LINE
           .. aux._MSG_SERVER_STARTED_2)
    end)

    print(aux._MSG_SERVER_STARTED_1 .. port_number .. aux._NEW_LINE
       .. aux._MSG_SERVER_STARTED_2)

    return ret
end

-- Helper function. Makes final buffer cleanups, closes streams, etc.
local _cleanups_fixate = function()
    -- TODO: Implement cleanup stuff.
end

-- Helper function. Draws a horizontal separator banner.
local _separator_draw = function(banner_text)
    local i = banner_text:len()
    local s = aux._EMPTY_STRING

    repeat s = s .. '=' i = i - 1 until (i == 0) print(s)
end

-- The daemon entry point.
local main = function(argc, argv)
    local ret = aux._EXIT_SUCCESS

    local daemon_name = path.basename(argv[1])
    local port_number = tonumber(argv[2], 10)

    _separator_draw(aux._DMN_DESCRIPTION)

    print(aux._DMN_NAME         .. aux._COMMA_SPACE_SEP .. aux._DMN_VERSION_S__
       .. aux._ONE_SPACE_STRING .. aux._DMN_VERSION      .. aux._NEW_LINE
       .. aux._DMN_DESCRIPTION                           .. aux._NEW_LINE
       .. aux._DMN_COPYRIGHT__  .. aux._ONE_SPACE_STRING .. aux._DMN_AUTHOR)

    _separator_draw(aux._DMN_DESCRIPTION)

    -- Checking for args presence.
    if (argc ~= 2) then
        ret = aux._EXIT_FAILURE

        print(daemon_name .. aux._ERR_MUST_BE_THE_ONLY_ARG_1
            .. (argc - 1) .. aux._ERR_MUST_BE_THE_ONLY_ARG_2
            .. aux._NEW_LINE)
<commit_msg>luvit: Print debug info regarding HTTP requests; emitting events explicitly.<commit_after>#!/usr/bin/env luvit
--[[ src/lua/dnsresolvd.lua
 * ============================================================================
 * DNS Resolver Daemon (dnsresolvd). Version 0.1
 * ============================================================================
 * A daemon that performs DNS lookups for the given hostname
 * passed in an HTTP request, with the focus on its implementation
 * using various programming languages. (Luvit-boosted impl.)
 * ============================================================================
 * Copyright (C) 2017 Radislav (Radicchio) Golubtsov
 *
 * (See the LICENSE file at the top of the source tree.)
--]]

local path = require("path")
local http = require("http")
local url  = require("url")
local dns  = require("dns")

local pp   = require("pretty-print")

local aux  = require("dnsresolvh")

-- Helper function. Draws a horizontal separator banner.
local _separator_draw = function(banner_text)
    local i = banner_text:len()
    local s = aux._EMPTY_STRING

    repeat s = s .. '=' i = i - 1 until (i == 0) print(s)
end

-- Helper function. Makes final buffer cleanups, closes streams, etc.
local _cleanups_fixate = function()
    -- TODO: Implement cleanup stuff.
end

--[[
 * Performs DNS lookup action for the given hostname,
 * i.e. (in this case) IP address retrieval by hostname.
 *
 * It first prepares and runs the server instance, then does all the rest.
 *
 * @param _ret        The status code passed in from the caller.
 * @param port_number The server port number to listen on.
 * @param daemon_name The daemon name (executable/script name).
 *
 * @return The status code indicating the daemon overall execution outcome.
--]]
local dns_lookup = function(_ret, port_number, daemon_name)
    local ret = _ret

    --[[
     * Creating, configuring, and starting the server.
     *
     * @param req  The HTTP request  object.
     * @param resp The HTTP response object.
     *
     * @return The HTTP server object.
    --]]
    local daemon = http.createServer(function(req, resp)
        _separator_draw(aux._DMN_DESCRIPTION)

        pp.prettyPrint(req)

        _separator_draw(aux._DMN_DESCRIPTION)

        -- Parsing and validating query params.
        local query = url.parse(req.url, true).query

        -- http://localhost:<port_number>/?h=<hostname>
        --                                 |
        local hostname = query.h -- <------+

        if (hostname == nil) then
            hostname = aux._DEF_HOSTNAME
        end

        --[[
         * Writes the response with the DNS record retrieved.
         *
         * @param hostname The effective hostname to look up for.
         * @param e        The Error object (if any error occurs).
         * @param rec      The DNS record retrieved.
        --]]
        local resp_write = function(hostname, e, rec)
            local resp_buffer = "<!DOCTYPE html>"                                                                               .. aux._NEW_LINE
.. "<html lang=\"en-US\" dir=\"ltr\">" .. aux._NEW_LINE .. "<head>"                                                             .. aux._NEW_LINE
.. "<meta http-equiv=\"Content-Type\"    content=\"" .. aux._HDR_CONTENT_TYPE .. "\" />"                                        .. aux._NEW_LINE
.. "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\" />"                                                       .. aux._NEW_LINE
.. "<!-- No caching at all for:                                                                       -->"                      .. aux._NEW_LINE
.. "<meta http-equiv=\"Cache-Control\"   content=\"" .. aux._HDR_CACHE_CONTROL .. "\" /> <!-- HTTP/1.1 -->"                     .. aux._NEW_LINE
.. "<meta http-equiv=\"Expires\"         content=\"" .. aux._HDR_EXPIRES .. "\"       /> <!-- Proxies  -->"                     .. aux._NEW_LINE
.. "<meta http-equiv=\"Pragma\"          content=\"" .. aux._HDR_PRAGMA .. "\"                            /> <!-- HTTP/1.0 -->" .. aux._NEW_LINE
.. "<meta       name=\"viewport\"        content=\"width=device-width,initial-scale=1\" />"                                     .. aux._NEW_LINE
.. "<meta       name=\"description\"     content=\"" .. aux._DMN_DESCRIPTION .. "\" />"                                         .. aux._NEW_LINE
.. "<title>" .. aux._DMN_NAME .. "</title>" .. aux._NEW_LINE .. "</head>"                                                       .. aux._NEW_LINE
.. "<body id=\"dnsresolvd\">"             .. aux._NEW_LINE .. "<p>"
.. hostname .. " ==&gt; "

            if (e ~= nil) then
                resp_buffer = resp_buffer .. aux._ERR_PREFIX
                                          .. aux._COLON_SPACE_SEP
                                          .. aux._ERR_COULD_NOT_LOOKUP
            else
                pp.prettyPrint(rec)

                if (#rec == 0) then
                    ret = aux._EXIT_FAILURE

                    return ret
                else
                    local addr = rec[1].address --> The IP address for host.
                    local ver  = rec[1].type    --> The IP version (family).

                        if (ver == dns.TYPE_A   ) then
                        ver = 4
                    elseif (ver == dns.TYPE_AAAA) then
                        ver = 6
                    end

                    resp_buffer = resp_buffer .. addr .. " (IPv" .. ver .. ")"
                end
            end

            resp_buffer = resp_buffer .. "</p>"    .. aux._NEW_LINE
                                      .. "</body>" .. aux._NEW_LINE
                                      .. "</html>" .. aux._NEW_LINE

            -- Adding headers to the response.
            resp:writeHead(aux._RSC_HTTP_200_OK, {
                ["Content-Type"]  = aux._HDR_CONTENT_TYPE,
                ["Cache-Control"] = aux._HDR_CACHE_CONTROL,
                ["Expires"]       = aux._HDR_EXPIRES,
                ["Pragma"]        = aux._HDR_PRAGMA
            })

            -- Writing the response out.
            resp:write(resp_buffer)

            -- Closing the response stream.
            resp:finish()
        end

        --[[
         * Performing DNS lookup for the given hostname
         * and writing the response out.
         *
         * Since Luvit actually doesn't have the dns.lookup() method,
         * it needs to perform a so-called two-stage lookup operation:
         *
         *     If the host doesn't have the A record (IPv4),
         *     trying to find its AAAA record (IPv6).
        --]]
        dns.resolve4        (hostname, function(e, rec)
            ret = resp_write(hostname,          e, rec)

            if (ret == aux._EXIT_FAILURE) then
                dns.resolve6  (hostname, function(e, rec)
                    resp_write(hostname,          e, rec)
                end)
            end
        end)
    end):listen(port_number)

    daemon:on(aux._EVE_ERROR, function(e)
        ret = aux._EXIT_FAILURE

        if (e == aux._ERR_EADDRINUSE) then
            print(daemon_name .. aux._ERR_CANNOT_START_SERVER
                              .. aux._ERR_SRV_PORT_IS_IN_USE
                              .. aux._NEW_LINE)
        else
            print(daemon_name .. aux._ERR_CANNOT_START_SERVER
                              .. aux._ERR_SRV_UNKNOWN_REASON
                              .. aux._NEW_LINE)
        end

        _cleanups_fixate()

        return ret
    end)

    daemon:on(aux._EVE_LISTENING, function(e)
        print(aux._MSG_SERVER_STARTED_1 .. port_number .. aux._NEW_LINE
           .. aux._MSG_SERVER_STARTED_2)
    end)

    pp.prettyPrint(daemon)

    -- FIXME: Investigate why we do need emitting events explicitly?
    --        This does not affect error events anyway, perplexedly.
    daemon:emit(aux._EVE_LISTENING                 )
--  daemon:emit(aux._EVE_ERROR, aux._ERR_EADDRINUSE)
--  daemon:emit(aux._EVE_ERROR                     )

    return ret
end

-- The daemon entry point.
local main = function(argc, argv)
    local ret = aux._EXIT_SUCCESS

    local daemon_name = path.basename(argv[1])
    local port_number = tonumber(argv[2], 10)

    _separator_draw(aux._DMN_DESCRIPTION)

    print(aux._DMN_NAME         .. aux._COMMA_SPACE_SEP .. aux._DMN_VERSION_S__
       .. aux._ONE_SPACE_STRING .. aux._DMN_VERSION      .. aux._NEW_LINE
       .. aux._DMN_DESCRIPTION                           .. aux._NEW_LINE
       .. aux._DMN_COPYRIGHT__  .. aux._ONE_SPACE_STRING .. aux._DMN_AUTHOR)

    _separator_draw(aux._DMN_DESCRIPTION)

    -- Checking for args presence.
    if (argc ~= 2) then
        ret = aux._EXIT_FAILURE

        print(daemon_name .. aux._ERR_MUST_BE_THE_ONLY_ARG_1
            .. (argc - 1) .. aux._ERR_MUST_BE_THE_ONLY_ARG_2
            .. aux._NEW_LINE)

{'fraction_non_alphanumeric': 0.13867232286172895, 'fraction_numerical': 0.004242146296720936, 'mean_word_length': 1.482214001138304, 'pattern_counts': {'":': 0, '<': 54, '<?xml version=': 0, '>': 56, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 64, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2935683', 'n_tokens_mistral': 4915, 'n_tokens_neox': 4718, 'n_words': 1200}