{"id":13792554,"url":"https://github.com/tarantool/http","last_synced_at":"2025-04-05T04:14:31.476Z","repository":{"id":14903365,"uuid":"17627270","full_name":"tarantool/http","owner":"tarantool","description":"Tarantool http server","archived":false,"fork":false,"pushed_at":"2024-11-15T08:19:05.000Z","size":405,"stargazers_count":82,"open_issues_count":28,"forks_count":39,"subscribers_count":41,"default_branch":"master","last_synced_at":"2025-03-29T03:11:21.479Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tarantool.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-03-11T10:56:47.000Z","updated_at":"2025-02-17T09:16:34.000Z","dependencies_parsed_at":"2024-08-03T22:06:47.268Z","dependency_job_id":"751b6b5e-b9dc-454c-a6d1-1dad37a0e26e","html_url":"https://github.com/tarantool/http","commit_stats":null,"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fhttp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fhttp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fhttp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tarantool%2Fhttp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tarantool","download_url":"https://codeload.github.com/tarantool/http/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247284953,"owners_count":20913704,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T22:01:13.635Z","updated_at":"2025-04-05T04:14:31.447Z","avatar_url":"https://github.com/tarantool.png","language":"Lua","funding_links":[],"categories":["Packages"],"sub_categories":["Clients"],"readme":"\u003ca href=\"http://tarantool.org\"\u003e\n   \u003cimg src=\"https://avatars2.githubusercontent.com/u/2344919?v=2\u0026s=250\"\nalign=\"right\"\u003e\n\u003c/a\u003e\n\n# HTTP server for Tarantool 1.7.5+\n\n[![Static analysis](https://github.com/tarantool/http/actions/workflows/check_on_push.yaml/badge.svg)](https://github.com/tarantool/http/actions/workflows/check_on_push.yaml)\n[![Test](https://github.com/tarantool/http/actions/workflows/test.yml/badge.svg)](https://github.com/tarantool/http/actions/workflows/test.yml)\n[![Coverage Status](https://coveralls.io/repos/github/tarantool/http/badge.svg?branch=master)](https://coveralls.io/github/tarantool/http?branch=master)\n\n\u003e **Note:** In Tarantool 1.7.5+, a full-featured HTTP client is available aboard.\n\u003e For Tarantool 1.6.5+, both HTTP server and client are available\n\u003e [here](https://github.com/tarantool/http/tree/tarantool-1.6).\n\n## http v2 has gone\n\nhttp v2 that was implemented in\n[#90](https://github.com/tarantool/http/issues/90) has been reverted in a\nmaster branch (commits\n[01004d7..e7e00ea](https://github.com/tarantool/http/compare/01004d7..e7e00ea))\nand a limited number of reverted commits were reimplemented on top of http v1.\nHowever http v2 changes are still available in a [branch\nhttp-v2-legacy](https://github.com/tarantool/http/tree/http-v2-legacy) as well as Lua\nrockspecs available with name `http-v2-legacy` instead of `http`. For reasons of\nhttp v2 revert and decisions regarding each reverted commit see\n[#134](https://github.com/tarantool/http/issues/134).\n\n## Table of contents\n\n* [Prerequisites](#prerequisites)\n* [Installation](#installation)\n* [Usage](#usage)\n* [Creating a server](#creating-a-server)\n* [Using routes](#using-routes)\n* [Contents of app\\_dir](#contents-of-app_dir)\n* [Route handlers](#route-handlers)\n  * [Fields and methods of the Request object](#fields-and-methods-of-the-request-object)\n  * [Fields and methods of the Response object](#fields-and-methods-of-the-response-object)\n  * [Examples](#examples)\n* [Working with stashes](#working-with-stashes)\n  * [Special stash names](#special-stash-names)\n* [Working with cookies](#working-with-cookies)\n* [Rendering a template](#rendering-a-template)\n* [Template helpers](#template-helpers)\n* [Hooks](#hooks)\n  * [handler(httpd, req)](#handlerhttpd-req)\n  * [before\\_dispatch(httpd, req)](#before_dispatchhttpd-req)\n  * [after\\_dispatch(cx, resp)](#after_dispatchcx-resp)\n* [Using a special socket](#using-a-special-socket)\n* [Roles](#roles)\n  * [roles.httpd](#roleshttpd)\n* [See also](#see-also)\n\n## Prerequisites\n\n * Tarantool 1.7.5+ with header files (`tarantool` \u0026\u0026 `tarantool-dev` packages)\n\n## Installation\n\nYou can:\n\n* clone the repository and build the `http` module using CMake:\n\n  ``` bash\n  git clone https://github.com/tarantool/http.git\n  cd http \u0026\u0026 cmake . -DCMAKE_BUILD_TYPE=RelWithDebugInfo\n  make\n  make install\n  ```\n\n* install the `http` module using [tt](https://github.com/tarantool/tt):\n\n  ``` bash\n  tt rocks install http\n  ```\n\n* install the `http` module using LuaRocks\n  (see [TarantoolRocks](https://github.com/tarantool/rocks) for\n  LuaRocks configuration details):\n\n  ``` bash\n  luarocks --server=https://rocks.tarantool.org/ --local install http\n  ```\n\n## Usage\n\nThe server is an object which is configured with HTTP request\nhandlers, routes (paths), templates, and a port to bind to.\nUnless Tarantool is running under a superuser, port numbers\nbelow 1024 may be unavailable.\n\nThe server can be started and stopped anytime. Multiple\nservers can be created.\n\nTo start a server:\n\n1. [Create it](#creating-a-server) with `httpd = require('http.server').new(...)`.\n2. [Configure routing](#using-routes) with `httpd:route(...)`.\n3. Start it with `httpd:start()`.\n\nTo stop the server, use `httpd:stop()`.\n\n## Creating a server\n\n```lua\nhttpd = require('http.server').new(host, port[, { options } ])\n```\n\n`host` and `port` must contain:\n* For tcp socket: the host and port to bind to.\n* For unix socket: `unix/` and path to socket (for example `/tmp/http-server.sock`) to bind to.\n\n`options` may contain:\n\n* `max_header_size` (default is 4096 bytes) - a limit for\n  HTTP request header size.\n* `header_timeout` (default: 100 seconds) - a timeout until\n  the server stops reading HTTP headers sent by the client.\n  The server closes the client connection if the client doesn't\n  send its headers within the given amount of time.\n* `app_dir` (default is '.', the server working directory) -\n  a path to the directory with HTML templates and controllers.\n* `handler` - a Lua function to handle HTTP requests (this is\n  a handler to use if the module \"routing\" functionality is not\n  needed).\n* `charset` - the character set for server responses of\n  type `text/html`, `text/plain` and `application/json`.\n* `display_errors` - return application errors and backtraces to the client\n  (like PHP). Disabled by default (since 1.2.0).\n* `log_requests` - log incoming requests. This parameter can receive:\n    - function value, supporting C-style formatting: log_requests(fmt, ...), where fmt is a format string and ... is Lua Varargs, holding arguments to be replaced in fmt.\n    - boolean value, where `true` choose default `log.info` and `false` disable request logs at all.\n\n  By default uses `log.info` function for requests logging.\n* `log_errors` - same as the `log_requests` option but is used for error messages logging. By default uses `log.error()` function.\n* `disable_keepalive` - disables keep-alive connections with misbehaving\n  clients. Parameter accept a table that contains a user agents names.\n  By default table is empty.\n\n  Example:\n\n  ```lua\n  local httpd = http_server.new('127.0.0.1', 8080, {\n      log_requests = true,\n      log_errors = true,\n      disable_keepalive = { 'curl/7.68.0' }\n  })\n  ```\n\n* `idle_timeout` - maximum amount of time an idle (keep-alive) connection will\n  remain idle before closing. When the idle timeout is exceeded, HTTP server\n  closes the keepalive connection. Default value: 0 seconds (disabled).\n* TLS options (to enable it, provide at least one of the following parameters):\n    * `ssl_cert_file` is a path to the SSL cert file, mandatory;\n    * `ssl_key_file` is a path to the SSL key file, mandatory;\n    * `ssl_ca_file` is a path to the SSL CA file, optional;\n    * `ssl_ciphers` is a colon-separated list of SSL ciphers, optional;\n    * `ssl_password` is a password for decrypting SSL private key, optional;\n    * `ssl_password_file` is a SSL file with key for decrypting SSL private key, optional.\n\n## Using routes\n\nIt is possible to automatically route requests between different\nhandlers, depending on the request path. The routing API is inspired\nby [Mojolicious](http://mojolicio.us/perldoc/Mojolicious/Guides/Routing) API.\n\nRoutes can be defined using:\n\n* an exact match (e.g. \"index.php\")\n* simple regular expressions\n* extended regular expressions\n\nRoute examples:\n\n```text\n'/'                 -- a simple route\n'/abc'              -- a simple route\n'/abc/:cde'         -- a route using a simple regular expression\n'/abc/:cde/:def'    -- a route using a simple regular expression\n'/ghi*path'         -- a route using an extended regular expression\n```\n\nTo configure a route, use the `route()` method of the `httpd` object:\n\n```lua\nhttpd:route({ path = '/path/to' }, 'controller#action')\nhttpd:route({ path = '/', template = 'Hello \u003c%= var %\u003e' }, handle1)\nhttpd:route({ path = '/:abc/cde', file = 'users.html.el' }, handle2)\nhttpd:route({ path = '/objects', method = 'GET' }, handle3)\n...\n```\n\nTo delete a named route, use `delete()` method of the `httpd` object:\n\n```lua\nhttpd:route({ path = '/path/to', name = 'route' }, 'controller#action')\nhttpd:delete('route')\n```\n\nThe first argument for `route()` is a Lua table with one or more keys:\n\n* `file` - a template file name (can be relative to.\n  `{app_dir}/templates`, where `app_dir` is the path set when creating the\n  server). If no template file name extension is provided, the extension is\n  set to \".html.el\", meaning HTML with embedded Lua.\n* `template` - template Lua variable name, in case the template\n  is a Lua variable. If `template` is a function, it's called on every\n  request to get template body. This is useful if template body must be\n  taken from a database.\n* `path` - route path, as described earlier.\n* `name` - route name.\n* `method` - method on the route like `POST`, `GET`, `PUT`, `DELETE`\n* `log_requests` - option that overrides the server parameter of the same name but only for current route.\n* `log_errors` - option that overrides the server parameter of the same name but only for current route.\n\nThe second argument is the route handler to be used to produce\na response to the request.\n\nThe typical usage is to avoid passing `file` and `template` arguments,\nsince they take time to evaluate, but these arguments are useful\nfor writing tests or defining HTTP servers with just one \"route\".\n\nThe handler can also be passed as a string of the form 'filename#functionname'.\nIn that case, the handler body is taken from a file in the\n`{app_dir}/controllers` directory.\n\n## Contents of `app_dir`\n\n* `public` - a path to static content. Everything stored on this path\n  defines a route which matches the file name, and the HTTP server serves this\n  file automatically, as is. Notice that the server doesn't use `sendfile()`,\n  and it reads the entire content of the file into the memory before passing\n  it to the client. ??? Caching is not used, unless turned on. So this is not\n  suitable for large files, use nginx instead.\n* `templates` -  a path to templates.\n* `controllers` - a path to *.lua files with Lua controllers. For example,\n  the controller name 'module.submodule#foo' is mapped to\n  `{app_dir}/controllers/module.submodule.lua`.\n\n## Route handlers\n\nA route handler is a function which accepts one argument (**Request**) and\nreturns one value (**Response**).\n\n```lua\nfunction my_handler(req)\n    -- req is a Request object\n    -- resp is a Response object\n    local resp = req:render({text = req.method..' '..req.path })\n    resp.headers['x-test-header'] = 'test';\n    resp.status = 201\n    return resp\nend\n```\n\n### Fields and methods of the Request object\n\n* `req.method` - HTTP request type (`GET`, `POST` etc).\n* `req.path` - request path.\n* `req.path_raw` - request path without decoding.\n* `req.query` - request arguments.\n* `req.proto` - HTTP version (for example, `{ 1, 1 }` is `HTTP/1.1`).\n* `req.headers` - normalized request headers. A normalized header\n  is in the lower case, all headers joined together into a single string.\n* `req.peer` - a Lua table with information about the remote peer\n  (like `socket:peer()`).\n* `tostring(req)` - returns a string representation of the request.\n* `req:request_line()` - returns a first line of the http request (for example, `PUT /path HTTP/1.1`).\n* `req:read(delimiter|chunk|{delimiter = x, chunk = x}, timeout)` - reads the\n  raw request body as a stream (see `socket:read()`).\n* `req:json()` - returns a Lua table from a JSON request.\n* `req:post_param(name)` - returns a single POST request a parameter value.\n  If `name` is `nil`, returns all parameters as a Lua table.\n* `req:query_param(name)` - returns a single GET request parameter value.\n  If `name` is `nil`, returns a Lua table with all arguments.\n* `req:param(name)` - any request parameter, either GET or POST.\n* `req:cookie(name, {raw = true})` | to get a cookie in the request. if `raw`\n  option was set then cookie will not be unescaped, otherwise cookie's value\n  will be unescaped.\n* `req:stash(name[, value])` - get or set a variable \"stashed\"\n  when dispatching a route.\n* `req:url_for(name, args, query)` - returns the route's exact URL.\n* `req:render({})` - create a **Response** object with a rendered template.\n* `req:redirect_to` - create a **Response** object with an HTTP redirect.\n\n### Fields and methods of the Response object\n\n* `resp.status` - HTTP response code.\n* `resp.headers` - a Lua table with normalized headers.\n* `resp.body` - response body (string|table|wrapped\\_iterator).\n* `resp:setcookie({ name = 'name', value = 'value', path = '/', expires = '+1y', domain = 'example.com'}, {raw = true})` -\n  adds `Set-Cookie` headers to `resp.headers`, if `raw` option was set then cookie will not be escaped,\n  otherwise cookie's value and path will be escaped\n\n### Examples\n\n```lua\nfunction my_handler(req)\n    return {\n        status = 200,\n        headers = { ['content-type'] = 'text/html; charset=utf8' },\n        body = [[\n            \u003chtml\u003e\n                \u003cbody\u003eHello, world!\u003c/body\u003e\n            \u003c/html\u003e\n        ]]\n    }\nend\n```\n\n## Working with stashes\n\n```lua\nfunction hello(self)\n    local id = self:stash('id')    -- here is :id value\n    local user = box.space.users:select(id)\n    if user == nil then\n        return self:redirect_to('/users_not_found')\n    end\n    return self:render({ user = user  })\nend\n\nhttpd = httpd.new('127.0.0.1', 8080)\nhttpd:route(\n    { path = '/:id/view', template = 'Hello, \u003c%= user.name %\u003e' }, hello)\nhttpd:start()\n```\n\n### Special stash names\n\n* `controller` - the controller name.\n* `action` - the handler name in the controller.\n* `format` - the current output format (e.g. `html`, `txt`). Is\n  detected automatically based on the request's `path` (for example, `/abc.js`\n  sets `format` to `js`). When producing a response, `format` is used\n  to serve the response's 'Content-type:'.\n\n## Working with cookies\n\nTo get a cookie, use:\n\n```lua\nfunction show_user(self)\n    local uid = self:cookie('id')\n\n    if uid ~= nil and string.match(uid, '^%d$') ~= nil then\n        local user = box.select(users, 0, uid)\n        return self:render({ user = user })\n    end\n\n    return self:redirect_to('/login')\nend\n```\n\nTo set a cookie, use the `setcookie()` method of a response object and pass to\nit a Lua table defining the cookie to be set:\n\n```lua\nfunction user_login(self)\n    local login = self:param('login')\n    local password = self:param('password')\n\n    local user = box.select(users, 1, login, password)\n    if user ~= nil then\n        local resp = self:redirect_to('/')\n        resp:setcookie({ name = 'uid', value = user[0], expires = '+1y' })\n        return resp\n    end\n\n    -- to login again and again and again\n    return self:redirect_to('/login')\nend\n```\n\nThe table must contain the following fields:\n\n* `name`\n* `value`\n* `path` (optional; if not set, the current request path is used)\n* `domain` (optional)\n* `expires` - cookie expire date, or expire offset, for example:\n\n  * `1d`  - 1 day\n  * `+1d` - the same\n  * `23d` - 23 days\n  * `+1m` - 1 month (30 days)\n  * `+1y` - 1 year (365 days)\n\n## Rendering a template\n\nLua can be used inside a response template, for example:\n\n```html\n\u003chtml\u003e\n    \u003chead\u003e\n        \u003ctitle\u003e\u003c%= title %\u003e\u003c/title\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n        \u003cul\u003e\n            % for i = 1, 10 do\n                \u003cli\u003e\u003c%= item[i].key %\u003e: \u003c%= item[i].value %\u003e\u003c/li\u003e\n            % end\n        \u003c/ul\u003e\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nTo embed Lua code into a template, use:\n\n* `\u003c% lua-here %\u003e` - insert any Lua code, including multi-line.\n  Can be used anywhere in the template.\n* `% lua-here` - a single-line Lua substitution. Can only be\n  present at the beginning of a line (with optional preceding spaces\n  and tabs, which are ignored).\n\nA few control characters may follow `%`:\n\n* `=` (e.g., `\u003c%= value + 1 %\u003e`) - runs the embedded Lua code\n  and inserts the result into HTML. Special HTML characters,\n  such as `\u003c`, `\u003e`, `\u0026`, `\"`, are escaped.\n* `==` (e.g., `\u003c%== value + 10 %\u003e`) - the same, but without\n  escaping.\n\nA Lua statement inside the template has access to the following\nenvironment:\n\n1. Lua variables defined in the template,\n1. stashed variables,\n1. variables standing for keys in the `render` table.\n\n## Template helpers\n\nHelpers are special functions that are available in all HTML\ntemplates. These functions must be defined when creating an `httpd` object.\n\nSetting or deleting a helper:\n\n```lua\n-- setting a helper\nhttpd:helper('time', function(self, ...) return box.time() end)\n-- deleting a helper\nhttpd:helper('some_name', nil)\n```\n\nUsing a helper inside an HTML template:\n\n```html\n\u003cdiv\u003e\n    Current timestamp: \u003c%= time() %\u003e\n\u003c/div\u003e\n```\n\nA helper function can receive arguments. The first argument is\nalways the current controller. The rest is whatever is\npassed to the helper from the template.\n\n## Hooks\n\nIt is possible to define additional functions invoked at various\nstages of request processing.\n\n### `handler(httpd, req)`\n\nIf `handler` is present in `httpd` options, it gets\ninvolved on every HTTP request, and the built-in routing\nmechanism is unused (no other hooks are called in this case).\n\n### `before_dispatch(httpd, req)`\n\nIs invoked before a request is routed to a handler. The first\nargument of the hook is the HTTP request to be handled.\nThe return value of the hook is ignored.\n\nThis hook could be used to log a request, or modify request headers.\n\n### `after_dispatch(cx, resp)`\n\nIs invoked after a handler for a route is executed.\n\nThe arguments of the hook are the request passed into the handler,\nand the response produced by the handler.\n\nThis hook can be used to modify the response.\nThe return value of the hook is ignored.\n\n## Using a special socket\n\nTo use a special socket, override the `tcp_server_f` field of the HTTP server\nobject with your own function. The function should return an object similar to\none returned by [socket.tcp_server][socket_ref]. It should call `opts.handler`\nwhen a connection occurs and provide `read`, `write` and `close` methods.\n\nExample:\n\n```lua\nlocal httpd = require('http.server')\nlocal server = httpd.new(settings.host, settings.port)\n\n-- Use sslsocket.\nlocal sslsocket = require('sslsocket')\nserver.tcp_server_f = sslsocket.tcp_server\n\n-- Or use your own handler.\nserver.tcp_server_f = function(host, port, opts)\n    assert(type(opts) == 'table')\n    local name = opts.name\n    local accept_handler = opts.handler\n    local http_server = opts.http_server\n\n    \u003c...\u003e\n    return \u003c..tcp server object..\u003e\nend\n\nserver:route(\u003cyour settings\u003e)\nserver:start()\n```\n\n[socket_ref]: https://www.tarantool.io/en/doc/latest/reference/reference_lua/socket/#socket-tcp-server\n\n## Roles\n\nTarantool 3 roles could be accessed from this project.\n\n### `roles.httpd`\n\nIt allows configuring one or more HTTP servers. Those servers could be reused\nby several other roles.\n\nExample of the configuration:\n\n```yaml\nroles_cfg:\n  roles.httpd:\n    default:\n      listen: 8081\n    additional:\n      listen: '127.0.0.1:8082'\n```\n\nServer address should be provided either as a URI or as a single port\n(in this case, `0.0.0.0` address is used).\n\nUser can access every working HTTP server from the configuration by name,\nusing `require('roles.httpd').get_server(name)` method.\nIf the `name` argument is `nil`, the default server is returned\n(its name should be equal to constant\n`require('roles.httpd').DEFAULT_SERVER_NAME`, which is `\"default\"`).\n\nLet's look at the example of using this role. Consider a new role\n`roles/hello_world.lua`:\n```lua\nlocal M = { dependencies = { 'roles.httpd' } }\nlocal server = {}\n\nM.validate = function(conf)\n    if conf == nil or conf.httpd == nil then\n        error(\"httpd must be set\")\n    end\n    local server = require('roles.httpd').get_server(conf.httpd)\n    if server == nil then\n        error(\"the httpd server \" .. conf.httpd .. \" not found\")\n    end\nend\n\nM.apply = function(conf)\n    server = require('roles.httpd').get_server(conf.httpd)\n\n    server:route({\n        path = '/hello/world',\n        name = 'greeting',\n    }, function(tx)\n        return tx:render({text = 'Hello, world!'})\n    end)\nend\n\nM.stop = function()\n    server:delete('greeting')\nend\n\nreturn M\n```\n\nTo enable TLS, provide the following params into roles config (for proper work\nit's enough to provide only `ssl_key_file` and `ssl_cert_file`):\n\n```yaml\nroles_cfg:\n  roles.httpd:\n    default:\n        listen: 8081\n        ssl_key_file: \"path/to/key/file\"\n        ssl_cert_file: \"path/to/key/file\"\n        ssl_ca_file: \"path/to/key/file\"\n        ssl_ciphers: \"cipher1:cipher2\"\n        ssl_password: \"password\"\n        ssl_password_file: \"path/to/ssl/password\"\n```\n\nThis role accepts a server by name from a config and creates a route to return \n`Hello, world!` to every request by this route.\n\nThen we need to write a simple config to start the Tarantool instance via\n`tt`:\n```yaml\napp:\n  file: 'myapp.lua'\n\ngroups:\n  group001:\n    replicasets:\n      replicaset001:\n        roles: [roles.httpd, roles.hello_world]\n        roles_cfg:\n          roles.httpd:\n            default:\n              listen: 8081\n            additional:\n              listen: '127.0.0.1:8082'\n          roles.hello_world:\n            httpd: 'additional'\n        instances:\n          instance001:\n            iproto:\n              listen:\n                - uri: '127.0.0.1:3301'\n```\n\nNext step, we need to start this instance using `tt start`:\n```bash\n$ tt start\n   • Starting an instance [app:instance001]...\n$ tt status\n INSTANCE         STATUS   PID      MODE \n app:instance001  RUNNING  2499387  RW\n```\n\nAnd then, we can get the greeting by running a simple curl command from a\nterminal:\n```bash\n$ curl http://127.0.0.1:8082/hello/world\nHello, world!\n```\n\n## See also\n\n * [Tarantool project][Tarantool] on GitHub\n * [Tests][] for the `http` module\n\n[Tarantool]: http://github.com/tarantool/tarantool\n[Tests]: https://github.com/tarantool/http/tree/master/test\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarantool%2Fhttp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftarantool%2Fhttp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftarantool%2Fhttp/lists"}