NAME

    Async::Microservice - Async HTTP Microservice Moose Role

SYNOPSIS

        # lib/Async/Microservice/HelloWorld.pm
        package Async::Microservice::HelloWorld;
        use Moose;
        with qw(Async::Microservice);
        sub service_name {return 'asmi-helloworld';}
        sub get_routes {return ('hello' => {defaults => {GET => 'GET_hello'}});}
        sub GET_hello {
            my ( $self, $this_req ) = @_;
            return [ 200, [], 'Hello world!' ];
        }
        1;
    
        # bin/async-microservice-helloworld.psgi
        use Async::Microservice::HelloWorld;
        my $mise = Async::Microservice::HelloWorld->new();
        return sub { $mise->plack_handler(@_) };
    
        $ plackup -Ilib --port 8089 --server Twiggy bin/async-microservice-helloworld.psgi
    
        $ curl http://localhost:8089/v1/hello
        Hello world!

DESCRIPTION

    This Moose::Role helps quickly bootstrap an async HTTP service that
    includes OpenAPI documentation.

    See https://time.meon.eu/ and the code in Async::Microservice::Time.

ATTRIBUTES

 static_path

    URL path prefix for OpenAPI files. Defaults to 'static'. Can be
    overridden by passing it to the constructor.

 file_placeholder

    Placeholder string used in OpenAPI files (like index.html and
    edit.html) that gets replaced with the service name. Defaults to
    'ASYNC-SERVICE-NAME'. Useful for templates that need to display the
    service name dynamically.

 Overriding Predefined Paths

    The following paths are provided by default:

      * / - Root index (OpenAPI documentation)

      * /static/:filename - OpenAPI files

      * /edit - OpenAPI editor

      * /hcheck - Health check endpoint

    You can override any of these default routes by defining them in your
    get_routes() method. Your custom routes take precedence over the
    defaults.

 To bootstrap a new async service

    Create a new package for your APIs using the current examples in
    lib/Async/Microservice/*. Set the return value of service_name. This
    string is used to set the process name and to locate the OpenAPI YAML
    definition for the documentation. Any GET/POST processing functions
    must be defined via get_routes.

    Copy one of the bin/*.psgi scripts and update it with your new package
    name.

    Copy one of root/static/*.yaml and rename it to match service_name.

    You can now launch the HTTP service with:

        plackup -Ilib --port 8089 --server Twiggy bin/async-microservice-YOURNAME.psgi

    In your browser, you can read the OpenAPI documentation:
    http://0.0.0.0:8089/v1/ and use the editor to extend it:
    http://0.0.0.0:8089/v1/edit

SEE ALSO

    OpenAPI Specification:
    https://github.com/OAI/OpenAPI-Specification/tree/master/versions or
    https://swagger.io/docs/specification/about/

    Async::MicroserviceReq Twiggy

TODO

        - graceful termination (finish all requests before terminating on sigterm/hup)
        - systemd service file examples
        - static/index.html and static/edit.html are not really static, should be moved

CONTRIBUTORS & CREDITS

    The following people have contributed to this distribution by
    committing their code, sending patches, reporting bugs, asking
    questions, suggesting useful advice, nitpicking, chatting on IRC or
    commenting on my blog (in no particular order):

        AI
        you?

    Also thanks to my current day-job-employer https://www.apa-it.at/.

BUGS

    Please report any bugs or feature requests via
    https://github.com/jozef/Async-Microservice/issues.

AUTHOR

    Jozef Kutej

COPYRIGHT & LICENSE

    Copyright 2020 Jozef Kutej, all rights reserved.

    This program is free software; you can redistribute it and/or modify it
    under the same terms as Perl itself.


----------------------------------------------------------------------------
NAME

    Async::MicroserviceReq - async microservice request class

SYNOPSIS

        my $this_req  = Async::MicroserviceReq->new(
            method     => $plack_req->method,
            headers    => $plack_req->headers,
            content    => $plack_req->content,
            path       => $plack_req->path_info,
            params     => $plack_req->parameters,
            static_dir => $self->static_dir,
        );
    
        ...
    
        my $plack_handler_sub = sub {
            my ($plack_respond) = @_;
            $this_req->plack_respond($plack_respond);
        ...

DESCRIPTION

    This is an object created for each request handled by
    Async::Microservice. It is passed to all request handling functions as
    the first argument and it provides request information and response
    helper methods.

ATTRIBUTES

        method
        headers
        path
        params
        plack_respond
        static_dir
        base_url
        want_json
        content
        json_content

METHODS

 text_plain(@text_lines)

    Send text plain response.

 respond($status, $headers, $payload)

    Send a PSGI/Plack response.

    $headers must be an array reference of header key/value pairs.

    If $payload is not a reference, it is sent as plain text by default.
    When the request Accept header allows JSON and no explicit Content-Type
    header is already present, plain scalar payloads are wrapped
    automatically as JSON:

        { "data": "..." }

    For error statuses ($status >= 400), scalar payloads are wrapped as:

        { "error": { err_status => ..., err_msg => ... } }

    If $payload is a reference, it is serialized as JSON. When JSONP is
    enabled and a valid callback parameter is present, the response is
    emitted as application/javascript instead of application/json.

 redirect($location_path)

    Send redirect.

 static_ft($file_name, $content_cb)

    Send static file, can be updated/modified using optional callback.

 get_pending_req

    Returns number of currently pending async requests.


----------------------------------------------------------------------------
NAME

    Async::Microservice::Time - example time async microservice

SYNOPSIS

        # can be started using:
        plackup --port 8085 -Ilib --access-log /dev/null --server Twiggy bin/async-microservice-time.psgi
    
        curl "http://localhost:8085/v1/hcheck" -H "accept: application/json"
        curl "http://localhost:8085/v1/epoch"  -H "accept: application/json"
        curl "http://localhost:8085/v1/datetime?time_zone=local" -H "accept: application/json"

DESCRIPTION

    This is an example asynchronous HTTP microservice using
    Async::Microservice. View the source code; it's minimal.

METHODS

 service_name

    Just a name, used to identify process and look for OpenAPI
    documentation.

 get_routes

    Path::Router configuration for dispatching

 http response methods

  GET_datetime

    https://time.meon.eu/v1/datetime

  POST_datetime

        $ curl -X POST "https://time.meon.eu/v1/datetime" -H  "accept: application/json" -H  "Content-Type: application/json" -d "{\"epoch\":-42}"
        {
           "date" : "1969-12-31",
           "datetime" : "1969-12-31 23:59:18 +0000",
           "day" : "31",
           "epoch" : -42,
           "hour" : "23",
           "minute" : "59",
           "month" : "12",
           "second" : "18",
           "time" : "23:59:18",
           "time_zone" : "+0000",
           "time_zone_name" : "UTC",
           "year" : "1969"
        }

  GET_epoch

    https://time.meon.eu/v1/epoch

  GET_sleep

    https://time.meon.eu/v1/sleep?duration=2.5

    This is the only response method processed in parallel (the other ones
    are pure CPU-bound) that sleeps for a given (or random) number of
    seconds and only then returns the request response with when it started
    and how long it took. Normally this is the same as what is in the
    duration parameter, but in case the server is overloaded with requests,
    the event loop may call the timer handler much later than the duration.
    Try:

        ab -n 1000 -c 500 http://localhost:8085/v1/sleep?duration=3
        Connection Times (ms)
                      min  mean[+/-sd] median   max
        Connect:        0  259 432.8     21    1033
        Processing:  3001 3090  72.5   3061    3253
        Waiting:     3001 3090  72.5   3061    3253
        Total:       3022 3349 394.1   3155    4065

    Then try to run together with 100% CPU load:

        ab -q -n 10000 -c 50 http://localhost:8085/v1/datetime

  the rest

    Check out Async::Microservice for built-in http response methods.

SEE ALSO

    t/02_Async-Microservice-Time.t for an example how to test this service.

