Enhance span fields and timer mechanism with service register
diff --git a/README.md b/README.md
index caba6c9..5713cc7 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@
 
 [![Twitter Follow](https://img.shields.io/twitter/follow/asfskywalking.svg?style=for-the-badge&label=Follow&logo=twitter)](https://twitter.com/AsfSkyWalking)
 
+![CI](https://github.com/apache/skywalking-nginx-lua/workflows/CI/badge.svg?branch=master)
+
 
 **SkyWalking** Nginx Agent provides the native tracing capability for Nginx powered by Nginx LUA module. 
 
diff --git a/examples/nginx.conf b/examples/nginx.conf
index 7829c58..2bcee14 100644
--- a/examples/nginx.conf
+++ b/examples/nginx.conf
@@ -29,6 +29,15 @@
     # Put the finished segment into this buffer as a queue
     lua_shared_dict segment_buffer 100m;
     lua_shared_dict metadata_buffer 1m;
+    
+    # Init is the timer setter and keeper
+    # Setup an infinite loop timer to do register and trace report.
+    init_worker_by_lua_block {
+        local metadata_buffer = ngx.shared.metadata_buffer
+        metadata_buffer['serviceName'] = 'Service Name'
+
+        require("tracing"):startTimer(metadata_buffer)
+    }
 
     server {
         listen 8080;
@@ -38,8 +47,10 @@
 
         location /ingress {
             default_type text/html;
+
             rewrite_by_lua_block {
                 local TC = require('tracing_context')
+                local Layer = require('span_layer')
 
                 local metadata_buffer = ngx.shared.metadata_buffer
                 -- Mock the service instance id
@@ -53,20 +64,35 @@
                     tracingContext = TC:newNoOP()
                 end
 
+                -- Constant pre-defined in SkyWalking main repo
+                -- 84 represents Nginx
+                local nginxComponentId = 6000
+
                 local contextCarrier = {}
                 contextCarrier["sw6"] = ngx.req.get_headers()["sw6"]
                 local entrySpan = tracingContext:createEntrySpan(ngx.var.uri, nil, contextCarrier)
                 entrySpan:start(ngx.now())
+                entrySpan:setComponentId(nginxComponentId)
+                entrySpan:setLayer(Layer.HTTP)
+                
+                entrySpan:tag('http.method', ngx.req.get_method())
+                entrySpan:tag('http.params', ngx.var.scheme .. '://' .. ngx.var.host .. ngx.var.request_uri )
                 
                 contextCarrier = {}
                 -- Use the same URI to represent incoming and forwarding requests
                 -- Change it if you need.
                 local upstreamUri = ngx.var.uri
+                ------------------------------------------------------
+                -- NOTICE, this should be changed manually
                 -- This variable represents the upstream logic address
                 -- Please set them as service logic name or DNS name
+                ------------------------------------------------------
                 local upstreamServerName = "upstream_ip:port"
+                ------------------------------------------------------
                 local exitSpan = tracingContext:createExitSpan(upstreamUri, entrySpan, upstreamServerName, contextCarrier)
-                entrySpan:start(ngx.now())
+                exitSpan:start(ngx.now())
+                exitSpan:setComponentId(nginxComponentId)
+                exitSpan:setLayer(Layer.HTTP)
                 
                 for name, value in pairs(contextCarrier) do
                     ngx.req.set_header(name, value)
@@ -93,12 +119,12 @@
                     ngx.ctx.entrySpan:finish()
                     local status, segment = ngx.ctx.tracingContext:drainAfterFinished()
                     if status then
-                        local segmentJson = segment:transform()
+                        local segmentJson = require('cjson').encode(segment:transform())
                         ngx.log(ngx.DEBUG, 'segment = ' .. segmentJson)
 
                         local queue = ngx.shared.segment_buffer
-                        local length = queue:lpush('segment', "abc")
-                        
+                        local length = queue:lpush('segment', segmentJson)
+                        ngx.log(ngx.DEBUG, 'segment buffer size = ' .. queue:llen('segment'))
                     end
                 end
             }
@@ -111,5 +137,14 @@
                 ngx.say("<p>Backend sw6 received headers: " .. ngx.req.get_headers()["sw6"] .. "</p>")
             }
         }
+
+        location /skywalking/register/service {
+            default_type text/html;
+            content_by_lua_block {
+                local registeredInfo = {}
+                registeredInfo[1] = {key="Service Name", value=1}
+                ngx.say(require('cjson').encode(registeredInfo))
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/lib/skywalking/register.lua b/lib/skywalking/register.lua
new file mode 100644
index 0000000..727b487
--- /dev/null
+++ b/lib/skywalking/register.lua
@@ -0,0 +1,53 @@
+-- 
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+-- 
+--    http://www.apache.org/licenses/LICENSE-2.0
+-- 
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+-- 
+
+local Services = {
+    services
+}
+
+local Service = {
+    serviceName,
+    -- Field type is optional, default value is `normal`
+    type = 'normal'
+}
+
+local ServiceInstances = {
+    instances
+}
+
+local ServiceInstance = {
+    serviceId,
+    instanceUUID,
+    time,
+    tags,
+    properties
+}
+
+local Register = {}
+
+-- Return Services as service register parameter
+function Register:newServiceRegister(serviceName)
+    local o = {}
+    setmetatable(o, Service)
+    Service.__index = Service
+
+    o.serviceName = serviceName
+
+    return o
+end
+
+return Register
\ No newline at end of file
diff --git a/lib/skywalking/resty/http.lua b/lib/skywalking/resty/http.lua
new file mode 100644
index 0000000..d9e05e6
--- /dev/null
+++ b/lib/skywalking/resty/http.lua
@@ -0,0 +1,1143 @@
+local http_headers = require "resty.http_headers"
+
+local ngx = ngx
+local ngx_socket_tcp = ngx.socket.tcp
+local ngx_req = ngx.req
+local ngx_req_socket = ngx_req.socket
+local ngx_req_get_headers = ngx_req.get_headers
+local ngx_req_get_method = ngx_req.get_method
+local str_lower = string.lower
+local str_upper = string.upper
+local str_find = string.find
+local str_sub = string.sub
+local tbl_concat = table.concat
+local tbl_insert = table.insert
+local ngx_encode_args = ngx.encode_args
+local ngx_re_match = ngx.re.match
+local ngx_re_gmatch = ngx.re.gmatch
+local ngx_re_sub = ngx.re.sub
+local ngx_re_gsub = ngx.re.gsub
+local ngx_re_find = ngx.re.find
+local ngx_log = ngx.log
+local ngx_DEBUG = ngx.DEBUG
+local ngx_ERR = ngx.ERR
+local ngx_var = ngx.var
+local ngx_print = ngx.print
+local ngx_header = ngx.header
+local co_yield = coroutine.yield
+local co_create = coroutine.create
+local co_status = coroutine.status
+local co_resume = coroutine.resume
+local setmetatable = setmetatable
+local tonumber = tonumber
+local tostring = tostring
+local unpack = unpack
+local rawget = rawget
+local select = select
+local ipairs = ipairs
+local pairs = pairs
+local pcall = pcall
+local type = type
+
+
+-- http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.1
+local HOP_BY_HOP_HEADERS = {
+    ["connection"]          = true,
+    ["keep-alive"]          = true,
+    ["proxy-authenticate"]  = true,
+    ["proxy-authorization"] = true,
+    ["te"]                  = true,
+    ["trailers"]            = true,
+    ["transfer-encoding"]   = true,
+    ["upgrade"]             = true,
+    ["content-length"]      = true, -- Not strictly hop-by-hop, but Nginx will deal
+                                    -- with this (may send chunked for example).
+}
+
+
+local EXPECTING_BODY = {
+    POST  = true,
+    PUT   = true,
+    PATCH = true,
+}
+
+
+-- Reimplemented coroutine.wrap, returning "nil, err" if the coroutine cannot
+-- be resumed. This protects user code from inifite loops when doing things like
+-- repeat
+--   local chunk, err = res.body_reader()
+--   if chunk then -- <-- This could be a string msg in the core wrap function.
+--     ...
+--   end
+-- until not chunk
+local co_wrap = function(func)
+    local co = co_create(func)
+    if not co then
+        return nil, "could not create coroutine"
+    else
+        return function(...)
+            if co_status(co) == "suspended" then
+                return select(2, co_resume(co, ...))
+            else
+                return nil, "can't resume a " .. co_status(co) .. " coroutine"
+            end
+        end
+    end
+end
+
+
+-- Returns a new table, recursively copied from the one given.
+--
+-- @param   table   table to be copied
+-- @return  table
+local function tbl_copy(orig)
+    local orig_type = type(orig)
+    local copy
+    if orig_type == "table" then
+        copy = {}
+        for orig_key, orig_value in next, orig, nil do
+            copy[tbl_copy(orig_key)] = tbl_copy(orig_value)
+        end
+    else -- number, string, boolean, etc
+        copy = orig
+    end
+    return copy
+end
+
+
+local _M = {
+    _VERSION = '0.14',
+}
+_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
+
+local mt = { __index = _M }
+
+
+local HTTP = {
+    [1.0] = " HTTP/1.0\r\n",
+    [1.1] = " HTTP/1.1\r\n",
+}
+
+
+local DEFAULT_PARAMS = {
+    method = "GET",
+    path = "/",
+    version = 1.1,
+}
+
+
+local DEBUG = false
+
+
+function _M.new(_)
+    local sock, err = ngx_socket_tcp()
+    if not sock then
+        return nil, err
+    end
+    return setmetatable({ sock = sock, keepalive = true }, mt)
+end
+
+
+function _M.debug(d)
+    DEBUG = (d == true)
+end
+
+
+function _M.set_timeout(self, timeout)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:settimeout(timeout)
+end
+
+
+function _M.set_timeouts(self, connect_timeout, send_timeout, read_timeout)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:settimeouts(connect_timeout, send_timeout, read_timeout)
+end
+
+
+function _M.ssl_handshake(self, ...)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    self.ssl = true
+
+    return sock:sslhandshake(...)
+end
+
+
+function _M.connect(self, ...)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    self.host = select(1, ...)
+    self.port = select(2, ...)
+
+    -- If port is not a number, this is likely a unix domain socket connection.
+    if type(self.port) ~= "number" then
+        self.port = nil
+    end
+
+    self.keepalive = true
+
+    return sock:connect(...)
+end
+
+
+function _M.set_keepalive(self, ...)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    if self.keepalive == true then
+        return sock:setkeepalive(...)
+    else
+        -- The server said we must close the connection, so we cannot setkeepalive.
+        -- If close() succeeds we return 2 instead of 1, to differentiate between
+        -- a normal setkeepalive() failure and an intentional close().
+        local res, err = sock:close()
+        if res then
+            return 2, "connection must be closed"
+        else
+            return res, err
+        end
+    end
+end
+
+
+function _M.get_reused_times(self)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:getreusedtimes()
+end
+
+
+function _M.close(self)
+    local sock = self.sock
+    if not sock then
+        return nil, "not initialized"
+    end
+
+    return sock:close()
+end
+
+
+local function _should_receive_body(method, code)
+    if method == "HEAD" then return nil end
+    if code == 204 or code == 304 then return nil end
+    if code >= 100 and code < 200 then return nil end
+    return true
+end
+
+
+function _M.parse_uri(_, uri, query_in_path)
+    if query_in_path == nil then query_in_path = true end
+
+    local m, err = ngx_re_match(uri, [[^(?:(http[s]?):)?//([^:/\?]+)(?::(\d+))?([^\?]*)\??(.*)]], "jo")
+
+    if not m then
+        if err then
+            return nil, "failed to match the uri: " .. uri .. ", " .. err
+        end
+
+        return nil, "bad uri: " .. uri
+    else
+        -- If the URI is schemaless (i.e. //example.com) try to use our current
+        -- request scheme.
+        if not m[1] then
+            local scheme = ngx_var.scheme
+            if scheme == "http" or scheme == "https" then
+                m[1] = scheme
+            else
+                return nil, "schemaless URIs require a request context: " .. uri
+            end
+        end
+
+        if m[3] then
+            m[3] = tonumber(m[3])
+        else
+            if m[1] == "https" then
+                m[3] = 443
+            else
+                m[3] = 80
+            end
+        end
+        if not m[4] or "" == m[4] then m[4] = "/" end
+
+        if query_in_path and m[5] and m[5] ~= "" then
+            m[4] = m[4] .. "?" .. m[5]
+            m[5] = nil
+        end
+
+        return m, nil
+    end
+end
+
+
+local function _format_request(params)
+    local version = params.version
+    local headers = params.headers or {}
+
+    local query = params.query or ""
+    if type(query) == "table" then
+        query = "?" .. ngx_encode_args(query)
+    elseif query ~= "" and str_sub(query, 1, 1) ~= "?" then
+        query = "?" .. query
+    end
+
+    -- Initialize request
+    local req = {
+        str_upper(params.method),
+        " ",
+        params.path,
+        query,
+        HTTP[version],
+        -- Pre-allocate slots for minimum headers and carriage return.
+        true,
+        true,
+        true,
+    }
+    local c = 6 -- req table index it's faster to do this inline vs table.insert
+
+    -- Append headers
+    for key, values in pairs(headers) do
+        key = tostring(key)
+
+        if type(values) == "table" then
+            for _, value in pairs(values) do
+                req[c] = key .. ": " .. tostring(value) .. "\r\n"
+                c = c + 1
+            end
+
+        else
+            req[c] = key .. ": " .. tostring(values) .. "\r\n"
+            c = c + 1
+        end
+    end
+
+    -- Close headers
+    req[c] = "\r\n"
+
+    return tbl_concat(req)
+end
+
+
+local function _receive_status(sock)
+    local line, err = sock:receive("*l")
+    if not line then
+        return nil, nil, nil, err
+    end
+
+    return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
+end
+
+
+local function _receive_headers(sock)
+    local headers = http_headers.new()
+
+    repeat
+        local line, err = sock:receive("*l")
+        if not line then
+            return nil, err
+        end
+
+        local m, err = ngx_re_match(line, "([^:\\s]+):\\s*(.*)", "jo")
+        if err then ngx_log(ngx_ERR, err) end
+
+        if not m then
+            break
+        end
+
+        local key = m[1]
+        local val = m[2]
+        if headers[key] then
+            if type(headers[key]) ~= "table" then
+                headers[key] = { headers[key] }
+            end
+            tbl_insert(headers[key], tostring(val))
+        else
+            headers[key] = tostring(val)
+        end
+    until ngx_re_find(line, "^\\s*$", "jo")
+
+    return headers, nil
+end
+
+
+local function _chunked_body_reader(sock, default_chunk_size)
+    return co_wrap(function(max_chunk_size)
+        local remaining = 0
+        local length
+        max_chunk_size = max_chunk_size or default_chunk_size
+
+        repeat
+            -- If we still have data on this chunk
+            if max_chunk_size and remaining > 0 then
+
+                if remaining > max_chunk_size then
+                    -- Consume up to max_chunk_size
+                    length = max_chunk_size
+                    remaining = remaining - max_chunk_size
+                else
+                    -- Consume all remaining
+                    length = remaining
+                    remaining = 0
+                end
+            else -- This is a fresh chunk
+
+                -- Receive the chunk size
+                local str, err = sock:receive("*l")
+                if not str then
+                    co_yield(nil, err)
+                end
+
+                length = tonumber(str, 16)
+
+                if not length then
+                    co_yield(nil, "unable to read chunksize")
+                end
+
+                if max_chunk_size and length > max_chunk_size then
+                    -- Consume up to max_chunk_size
+                    remaining = length - max_chunk_size
+                    length = max_chunk_size
+                end
+            end
+
+            if length > 0 then
+                local str, err = sock:receive(length)
+                if not str then
+                    co_yield(nil, err)
+                end
+
+                max_chunk_size = co_yield(str) or default_chunk_size
+
+                -- If we're finished with this chunk, read the carriage return.
+                if remaining == 0 then
+                    sock:receive(2) -- read \r\n
+                end
+            else
+                -- Read the last (zero length) chunk's carriage return
+                sock:receive(2) -- read \r\n
+            end
+
+        until length == 0
+    end)
+end
+
+
+local function _body_reader(sock, content_length, default_chunk_size)
+    return co_wrap(function(max_chunk_size)
+        max_chunk_size = max_chunk_size or default_chunk_size
+
+        if not content_length and max_chunk_size then
+            -- We have no length, but wish to stream.
+            -- HTTP 1.0 with no length will close connection, so read chunks to the end.
+            repeat
+                local str, err, partial = sock:receive(max_chunk_size)
+                if not str and err == "closed" then
+                    co_yield(partial, err)
+                end
+
+                max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
+                if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
+
+                if not max_chunk_size then
+                    ngx_log(ngx_ERR, "Buffer size not specified, bailing")
+                    break
+                end
+            until not str
+
+        elseif not content_length then
+            -- We have no length but don't wish to stream.
+            -- HTTP 1.0 with no length will close connection, so read to the end.
+            co_yield(sock:receive("*a"))
+
+        elseif not max_chunk_size then
+            -- We have a length and potentially keep-alive, but want everything.
+            co_yield(sock:receive(content_length))
+
+        else
+            -- We have a length and potentially a keep-alive, and wish to stream
+            -- the response.
+            local received = 0
+            repeat
+                local length = max_chunk_size
+                if received + length > content_length then
+                    length = content_length - received
+                end
+
+                if length > 0 then
+                    local str, err = sock:receive(length)
+                    if not str then
+                        co_yield(nil, err)
+                    end
+                    received = received + length
+
+                    max_chunk_size = tonumber(co_yield(str) or default_chunk_size)
+                    if max_chunk_size and max_chunk_size < 0 then max_chunk_size = nil end
+
+                    if not max_chunk_size then
+                        ngx_log(ngx_ERR, "Buffer size not specified, bailing")
+                        break
+                    end
+                end
+
+            until length == 0
+        end
+    end)
+end
+
+
+local function _no_body_reader()
+    return nil
+end
+
+
+local function _read_body(res)
+    local reader = res.body_reader
+
+    if not reader then
+        -- Most likely HEAD or 304 etc.
+        return nil, "no body to be read"
+    end
+
+    local chunks = {}
+    local c = 1
+
+    local chunk, err
+    repeat
+        chunk, err = reader()
+
+        if err then
+            return nil, err, tbl_concat(chunks) -- Return any data so far.
+        end
+        if chunk then
+            chunks[c] = chunk
+            c = c + 1
+        end
+    until not chunk
+
+    return tbl_concat(chunks)
+end
+
+
+local function _trailer_reader(sock)
+    return co_wrap(function()
+        co_yield(_receive_headers(sock))
+    end)
+end
+
+
+local function _read_trailers(res)
+    local reader = res.trailer_reader
+    if not reader then
+        return nil, "no trailers"
+    end
+
+    local trailers = reader()
+    setmetatable(res.headers, { __index = trailers })
+end
+
+
+local function _send_body(sock, body)
+    if type(body) == 'function' then
+        repeat
+            local chunk, err, partial = body()
+
+            if chunk then
+                local ok, err = sock:send(chunk)
+
+                if not ok then
+                    return nil, err
+                end
+            elseif err ~= nil then
+                return nil, err, partial
+            end
+
+        until chunk == nil
+    elseif body ~= nil then
+        local bytes, err = sock:send(body)
+
+        if not bytes then
+            return nil, err
+        end
+    end
+    return true, nil
+end
+
+
+local function _handle_continue(sock, body)
+    local status, version, reason, err = _receive_status(sock) --luacheck: no unused
+    if not status then
+        return nil, nil, err
+    end
+
+    -- Only send body if we receive a 100 Continue
+    if status == 100 then
+        local ok, err = sock:receive("*l") -- Read carriage return
+        if not ok then
+            return nil, nil, err
+        end
+        _send_body(sock, body)
+    end
+    return status, version, err
+end
+
+
+function _M.send_request(self, params)
+    -- Apply defaults
+    setmetatable(params, { __index = DEFAULT_PARAMS })
+
+    local sock = self.sock
+    local body = params.body
+    local headers = http_headers.new()
+
+    local params_headers = params.headers
+    if params_headers then
+        -- We assign one by one so that the metatable can handle case insensitivity
+        -- for us. You can blame the spec for this inefficiency.
+        for k, v in pairs(params_headers) do
+            headers[k] = v
+        end
+    end
+
+    -- Ensure minimal headers are set
+
+    if not headers["Content-Length"] then
+        if type(body) == 'string' then
+            headers["Content-Length"] = #body
+        elseif body == nil and EXPECTING_BODY[str_upper(params.method)] then
+            headers["Content-Length"] = 0
+        end
+    end
+    if not headers["Host"] then
+        if (str_sub(self.host, 1, 5) == "unix:") then
+            return nil, "Unable to generate a useful Host header for a unix domain socket. Please provide one."
+        end
+        -- If we have a port (i.e. not connected to a unix domain socket), and this
+        -- port is non-standard, append it to the Host heaer.
+        if self.port then
+            if self.ssl and self.port ~= 443 then
+                headers["Host"] = self.host .. ":" .. self.port
+            elseif not self.ssl and self.port ~= 80 then
+                headers["Host"] = self.host .. ":" .. self.port
+            else
+                headers["Host"] = self.host
+            end
+        else
+            headers["Host"] = self.host
+        end
+    end
+    if not headers["User-Agent"] then
+        headers["User-Agent"] = _M._USER_AGENT
+    end
+    if params.version == 1.0 and not headers["Connection"] then
+        headers["Connection"] = "Keep-Alive"
+    end
+
+    params.headers = headers
+
+    -- Format and send request
+    local req = _format_request(params)
+    if DEBUG then ngx_log(ngx_DEBUG, "\n", req) end
+    local bytes, err = sock:send(req)
+
+    if not bytes then
+        return nil, err
+    end
+
+    -- Send the request body, unless we expect: continue, in which case
+    -- we handle this as part of reading the response.
+    if headers["Expect"] ~= "100-continue" then
+        local ok, err, partial = _send_body(sock, body)
+        if not ok then
+            return nil, err, partial
+        end
+    end
+
+    return true
+end
+
+
+function _M.read_response(self, params)
+    local sock = self.sock
+
+    local status, version, reason, err
+
+    -- If we expect: continue, we need to handle this, sending the body if allowed.
+    -- If we don't get 100 back, then status is the actual status.
+    if params.headers["Expect"] == "100-continue" then
+        local _status, _version, _err = _handle_continue(sock, params.body)
+        if not _status then
+            return nil, _err
+        elseif _status ~= 100 then
+            status, version, err = _status, _version, _err -- luacheck: no unused
+        end
+    end
+
+    -- Just read the status as normal.
+    if not status then
+        status, version, reason, err = _receive_status(sock)
+        if not status then
+            return nil, err
+        end
+    end
+
+
+    local res_headers, err = _receive_headers(sock)
+    if not res_headers then
+        return nil, err
+    end
+
+    -- keepalive is true by default. Determine if this is correct or not.
+    local ok, connection = pcall(str_lower, res_headers["Connection"])
+    if ok then
+        if (version == 1.1 and str_find(connection, "close", 1, true)) or
+           (version == 1.0 and not str_find(connection, "keep-alive", 1, true)) then
+            self.keepalive = false
+        end
+    else
+        -- no connection header
+        if version == 1.0 then
+            self.keepalive = false
+        end
+    end
+
+    local body_reader = _no_body_reader
+    local trailer_reader, err
+    local has_body = false
+
+    -- Receive the body_reader
+    if _should_receive_body(params.method, status) then
+        has_body = true
+
+        local te = res_headers["Transfer-Encoding"]
+
+        -- Handle duplicate headers
+        -- This shouldn't happen but can in the real world
+        if type(te) == "table" then
+            te = tbl_concat(te, "")
+        end
+
+        local ok, encoding = pcall(str_lower, te)
+        if not ok then
+            encoding = ""
+        end
+
+        if version == 1.1 and str_find(encoding, "chunked", 1, true) ~= nil then
+            body_reader, err = _chunked_body_reader(sock)
+
+        else
+            local ok, length = pcall(tonumber, res_headers["Content-Length"])
+            if not ok then
+                -- No content-length header, read until connection is closed by server
+                length = nil
+            end
+
+            body_reader, err = _body_reader(sock, length)
+
+        end
+
+    end
+
+    if res_headers["Trailer"] then
+        trailer_reader, err = _trailer_reader(sock)
+    end
+
+    if err then
+        return nil, err
+    else
+        return {
+            status = status,
+            reason = reason,
+            headers = res_headers,
+            has_body = has_body,
+            body_reader = body_reader,
+            read_body = _read_body,
+            trailer_reader = trailer_reader,
+            read_trailers = _read_trailers,
+        }
+    end
+end
+
+
+function _M.request(self, params)
+    params = tbl_copy(params) -- Take by value
+    local res, err = self:send_request(params)
+    if not res then
+        return res, err
+    else
+        return self:read_response(params)
+    end
+end
+
+
+function _M.request_pipeline(self, requests)
+    requests = tbl_copy(requests) -- Take by value
+
+    for _, params in ipairs(requests) do
+        if params.headers and params.headers["Expect"] == "100-continue" then
+            return nil, "Cannot pipeline request specifying Expect: 100-continue"
+        end
+
+        local res, err = self:send_request(params)
+        if not res then
+            return res, err
+        end
+    end
+
+    local responses = {}
+    for i, params in ipairs(requests) do
+        responses[i] = setmetatable({
+            params = params,
+            response_read = false,
+        }, {
+            -- Read each actual response lazily, at the point the user tries
+            -- to access any of the fields.
+            __index = function(t, k)
+                local res, err
+                if t.response_read == false then
+                    res, err = _M.read_response(self, t.params)
+                    t.response_read = true
+
+                    if not res then
+                        ngx_log(ngx_ERR, err)
+                    else
+                        for rk, rv in pairs(res) do
+                            t[rk] = rv
+                        end
+                    end
+                end
+                return rawget(t, k)
+            end,
+        })
+    end
+    return responses
+end
+
+
+function _M.request_uri(self, uri, params)
+    params = tbl_copy(params or {}) -- Take by value
+
+    local parsed_uri, err = self:parse_uri(uri, false)
+    if not parsed_uri then
+        return nil, err
+    end
+
+    local scheme, host, port, path, query = unpack(parsed_uri)
+    if not params.path then params.path = path end
+    if not params.query then params.query = query end
+
+    -- See if we should use a proxy to make this request
+    local proxy_uri = self:get_proxy_uri(scheme, host)
+
+    -- Make the connection either through the proxy or directly
+    -- to the remote host
+    local c, err
+
+    if proxy_uri then
+        local proxy_authorization
+        if scheme == "https" then
+            if params.headers and params.headers["Proxy-Authorization"] then
+                proxy_authorization = params.headers["Proxy-Authorization"]
+            else
+                proxy_authorization = self.proxy_opts.https_proxy_authorization
+            end
+        end
+
+        c, err = self:connect_proxy(proxy_uri, scheme, host, port, proxy_authorization)
+    else
+        c, err = self:connect(host, port)
+    end
+
+    if not c then
+        return nil, err
+    end
+
+    if proxy_uri then
+        if scheme == "http" then
+            -- When a proxy is used, the target URI must be in absolute-form
+            -- (RFC 7230, Section 5.3.2.). That is, it must be an absolute URI
+            -- to the remote resource with the scheme, host and an optional port
+            -- in place.
+            --
+            -- Since _format_request() constructs the request line by concatenating
+            -- params.path and params.query together, we need to modify the path
+            -- to also include the scheme, host and port so that the final form
+            -- in conformant to RFC 7230.
+            if port == 80 then
+                params.path = scheme .. "://" .. host .. path
+            else
+                params.path = scheme .. "://" .. host .. ":" .. port .. path
+            end
+
+            if self.proxy_opts.http_proxy_authorization then
+                if not params.headers then
+                    params.headers = {}
+                end
+
+                if not params.headers["Proxy-Authorization"] then
+                    params.headers["Proxy-Authorization"] = self.proxy_opts.http_proxy_authorization
+                end
+            end
+        elseif scheme == "https" then
+            -- don't keep this connection alive as the next request could target
+            -- any host and re-using the proxy tunnel for that is not possible
+            self.keepalive = false
+        end
+
+        -- self:connect_uri() set the host and port to point to the proxy server. As
+        -- the connection to the proxy has been established, set the host and port
+        -- to point to the actual remote endpoint at the other end of the tunnel to
+        -- ensure the correct Host header added to the requests.
+        self.host = host
+        self.port = port
+    end
+
+    if scheme == "https" then
+        local verify = true
+
+        if params.ssl_verify == false then
+            verify = false
+        end
+
+        local ok, err = self:ssl_handshake(nil, host, verify)
+        if not ok then
+            self:close()
+            return nil, err
+        end
+
+    end
+
+    local res, err = self:request(params)
+    if not res then
+        self:close()
+        return nil, err
+    end
+
+    local body, err = res:read_body()
+    if not body then
+        self:close()
+        return nil, err
+    end
+
+    res.body = body
+
+    if params.keepalive == false then
+        local ok, err = self:close()
+        if not ok then
+            ngx_log(ngx_ERR, err)
+        end
+
+    else
+        local ok, err = self:set_keepalive(params.keepalive_timeout, params.keepalive_pool)
+        if not ok then
+            ngx_log(ngx_ERR, err)
+        end
+
+    end
+
+    return res, nil
+end
+
+
+function _M.get_client_body_reader(_, chunksize, sock)
+    chunksize = chunksize or 65536
+
+    if not sock then
+        local ok, err
+        ok, sock, err = pcall(ngx_req_socket)
+
+        if not ok then
+            return nil, sock -- pcall err
+        end
+
+        if not sock then
+            if err == "no body" then
+                return nil
+            else
+                return nil, err
+            end
+        end
+    end
+
+    local headers = ngx_req_get_headers()
+    local length = headers.content_length
+    local encoding = headers.transfer_encoding
+    if length then
+        return _body_reader(sock, tonumber(length), chunksize)
+    elseif encoding and str_lower(encoding) == 'chunked' then
+        -- Not yet supported by ngx_lua but should just work...
+        return _chunked_body_reader(sock, chunksize)
+    else
+        return nil
+    end
+end
+
+
+function _M.proxy_request(self, chunksize)
+    return self:request({
+        method = ngx_req_get_method(),
+        path = ngx_re_gsub(ngx_var.uri, "\\s", "%20", "jo") .. ngx_var.is_args .. (ngx_var.query_string or ""),
+        body = self:get_client_body_reader(chunksize),
+        headers = ngx_req_get_headers(),
+    })
+end
+
+
+function _M.proxy_response(_, response, chunksize)
+    if not response then
+        ngx_log(ngx_ERR, "no response provided")
+        return
+    end
+
+    ngx.status = response.status
+
+    -- Filter out hop-by-hop headeres
+    for k, v in pairs(response.headers) do
+        if not HOP_BY_HOP_HEADERS[str_lower(k)] then
+            ngx_header[k] = v
+        end
+    end
+
+    local reader = response.body_reader
+    repeat
+        local chunk, err = reader(chunksize)
+        if err then
+            ngx_log(ngx_ERR, err)
+            break
+        end
+
+        if chunk then
+            local res, err = ngx_print(chunk)
+            if not res then
+                ngx_log(ngx_ERR, err)
+                break
+            end
+        end
+    until not chunk
+end
+
+
+function _M.set_proxy_options(self, opts)
+    self.proxy_opts = tbl_copy(opts) -- Take by value
+end
+
+
+function _M.get_proxy_uri(self, scheme, host)
+    if not self.proxy_opts then
+        return nil
+    end
+
+    -- Check if the no_proxy option matches this host. Implementation adapted
+    -- from lua-http library (https://github.com/daurnimator/lua-http)
+    if self.proxy_opts.no_proxy then
+        if self.proxy_opts.no_proxy == "*" then
+            -- all hosts are excluded
+            return nil
+        end
+
+        local no_proxy_set = {}
+        -- wget allows domains in no_proxy list to be prefixed by "."
+        -- e.g. no_proxy=.mit.edu
+        for host_suffix in ngx_re_gmatch(self.proxy_opts.no_proxy, "\\.?([^,]+)") do
+            no_proxy_set[host_suffix[1]] = true
+        end
+
+        -- From curl docs:
+        -- matched as either a domain which contains the hostname, or the
+        -- hostname itself. For example local.com would match local.com,
+        -- local.com:80, and www.local.com, but not www.notlocal.com.
+        --
+        -- Therefore, we keep stripping subdomains from the host, compare
+        -- them to the ones in the no_proxy list and continue until we find
+        -- a match or until there's only the TLD left
+        repeat
+            if no_proxy_set[host] then
+                return nil
+            end
+
+            -- Strip the next level from the domain and check if that one
+            -- is on the list
+            host = ngx_re_sub(host, "^[^.]+\\.", "")
+        until not ngx_re_find(host, "\\.")
+    end
+
+    if scheme == "http" and self.proxy_opts.http_proxy then
+        return self.proxy_opts.http_proxy
+    end
+
+    if scheme == "https" and self.proxy_opts.https_proxy then
+        return self.proxy_opts.https_proxy
+    end
+
+    return nil
+end
+
+
+function _M.connect_proxy(self, proxy_uri, scheme, host, port, proxy_authorization)
+    -- Parse the provided proxy URI
+    local parsed_proxy_uri, err = self:parse_uri(proxy_uri, false)
+    if not parsed_proxy_uri then
+        return nil, err
+    end
+
+    -- Check that the scheme is http (https is not supported for
+    -- connections between the client and the proxy)
+    local proxy_scheme = parsed_proxy_uri[1]
+    if proxy_scheme ~= "http" then
+        return nil, "protocol " .. proxy_scheme .. " not supported for proxy connections"
+    end
+
+    -- Make the connection to the given proxy
+    local proxy_host, proxy_port = parsed_proxy_uri[2], parsed_proxy_uri[3]
+    local c, err = self:connect(proxy_host, proxy_port)
+    if not c then
+        return nil, err
+    end
+
+    if scheme == "https" then
+        -- Make a CONNECT request to create a tunnel to the destination through
+        -- the proxy. The request-target and the Host header must be in the
+        -- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section
+        -- 4.3.6 for more details about the CONNECT request
+        local destination = host .. ":" .. port
+        local res, err = self:request({
+            method = "CONNECT",
+            path = destination,
+            headers = {
+                ["Host"] = destination,
+                ["Proxy-Authorization"] = proxy_authorization,
+            }
+        })
+
+        if not res then
+            return nil, err
+        end
+
+        if res.status < 200 or res.status > 299 then
+            return nil, "failed to establish a tunnel through a proxy: " .. res.status
+        end
+    end
+
+    return c, nil
+end
+
+
+return _M
diff --git a/lib/skywalking/resty/http_headers.lua b/lib/skywalking/resty/http_headers.lua
new file mode 100644
index 0000000..6771d1e
--- /dev/null
+++ b/lib/skywalking/resty/http_headers.lua
@@ -0,0 +1,44 @@
+local rawget, rawset, setmetatable =
+    rawget, rawset, setmetatable
+
+local str_lower = string.lower
+
+local _M = {
+    _VERSION = '0.14',
+}
+
+
+-- Returns an empty headers table with internalised case normalisation.
+function _M.new()
+    local mt = {
+        normalised = {},
+    }
+
+    mt.__index = function(t, k)
+        return rawget(t, mt.normalised[str_lower(k)])
+    end
+
+    mt.__newindex = function(t, k, v)
+        local k_normalised = str_lower(k)
+
+        -- First time seeing this header field?
+        if not mt.normalised[k_normalised] then
+            -- Create a lowercased entry in the metatable proxy, with the value
+            -- of the given field case
+            mt.normalised[k_normalised] = k
+
+            -- Set the header using the given field case
+            rawset(t, k, v)
+        else
+            -- We're being updated just with a different field case. Use the
+            -- normalised metatable proxy to give us the original key case, and
+            -- perorm a rawset() to update the value.
+            rawset(t, mt.normalised[k_normalised], v)
+        end
+    end
+
+    return setmetatable({}, mt)
+end
+
+
+return _M
diff --git a/lib/skywalking/span.lua b/lib/skywalking/span.lua
index 3fafd01..e2a8eec 100644
--- a/lib/skywalking/span.lua
+++ b/lib/skywalking/span.lua
@@ -242,6 +242,15 @@
     return self
 end
 
+function Span:setLayer(spanLayer)
+    if self.is_noop then
+        return self
+    end
+    self.layer = spanLayer
+
+    return self
+end
+
 function Span:errorOccurred()
     if self.is_noop then
         return self
@@ -256,7 +265,7 @@
         return self
     end
 
-    self.tags[#self.tags + 1] = { key=value }
+    self.tags[key] = value
 
     return self
 end
@@ -299,7 +308,7 @@
         spanBuilder.spanType = 'Local'
     end
     if self.layer ~= spanLayer.NONE then
-        spanBuilder.spanLayer = self.span_layer.name
+        spanBuilder.spanLayer = self.layer.name
     end
     spanBuilder.componentId = self.component_id
     spanBuilder.isError = self.error_occurred
diff --git a/lib/skywalking/tracing.lua b/lib/skywalking/tracing.lua
new file mode 100644
index 0000000..1683e3d
--- /dev/null
+++ b/lib/skywalking/tracing.lua
@@ -0,0 +1,88 @@
+-- 
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+-- 
+--    http://www.apache.org/licenses/LICENSE-2.0
+-- 
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+-- 
+
+local Tracing = {}
+
+-- Tracing timer does the service and instance register
+-- After register successfully, it sends traces and heart beat
+function Tracing:startTimer(metadata_buffer)
+    -- The codes of timer setup is following the OpenResty timer doc
+    local delay = 3  -- in seconds
+    local new_timer = ngx.timer.at
+    local check
+
+    local log = ngx.log
+    local DEBUG = ngx.DEBUG
+
+    check = function(premature)
+        if not premature then
+            if metadata_buffer['serviceId'] == nil then
+                self:registerService(metadata_buffer)
+            end
+
+            -- do the health check
+            local ok, err = new_timer(delay, check)
+            if not ok then
+                log(ERR, "failed to create timer: ", err)
+                return
+            end
+        end
+    end
+
+    if 0 == ngx.worker.id() then
+        local ok, err = new_timer(delay, check)
+        if not ok then
+            log(ERR, "failed to create timer: ", err)
+            return
+        end
+    end
+end
+
+function Tracing:registerService(metadata_buffer)
+    local log = ngx.log
+    local DEBUG = ngx.DEBUG
+    
+    local serviceName = metadata_buffer['serviceName']
+    local cjson = require('cjson')
+    local serviceRegister = require("register").newServiceRegister(serviceName)
+    local serviceRegisterParam = cjson.encode(serviceRegister)
+
+    local http = require('resty.http')
+    local httpc = http.new()
+    local res, err = httpc:request_uri('http://127.0.0.1:8080/skywalking/register/service', {
+        method = "POST",
+        body = serviceRegisterParam,
+        headers = {
+            ["Content-Type"] = "application/json",
+        },
+    })
+
+    if #res.body > 0 then
+        local registerResults = cjson.decode(res.body)
+
+        for i, result in ipairs(registerResults)
+        do
+            if result.key == serviceName then
+                local serviceId = result.value 
+                log(DEBUG, "Service registered, service id = " .. serviceId)
+                metadata_buffer['serviceId'] = serviceId
+            end
+        end
+    end
+end
+
+return Tracing
\ No newline at end of file