summaryrefslogtreecommitdiff
path: root/gemini
diff options
context:
space:
mode:
Diffstat (limited to 'gemini')
-rw-r--r--gemini/client.go4
-rw-r--r--gemini/gemtext/fuzz_test.go2
-rw-r--r--gemini/gemtext/htmlconv/convert.go4
-rw-r--r--gemini/gemtext/htmlconv/convert_test.go4
-rw-r--r--gemini/gemtext/internal/templates.go2
-rw-r--r--gemini/gemtext/mdconv/convert.go4
-rw-r--r--gemini/gemtext/mdconv/convert_test.go4
-rw-r--r--gemini/gemtext/parse_line_test.go2
-rw-r--r--gemini/gemtext/parse_test.go2
-rw-r--r--gemini/request.go20
-rw-r--r--gemini/request_test.go2
-rw-r--r--gemini/response.go100
-rw-r--r--gemini/response_test.go20
-rw-r--r--gemini/roundtrip_test.go12
-rw-r--r--gemini/serve.go32
15 files changed, 109 insertions, 105 deletions
diff --git a/gemini/client.go b/gemini/client.go
index 4f99078..68c5488 100644
--- a/gemini/client.go
+++ b/gemini/client.go
@@ -7,7 +7,7 @@ import (
"io"
"net"
- "tildegit.org/tjp/gus"
+ sr "tildegit.org/tjp/sliderule"
)
// Client is used for sending gemini requests and parsing gemini responses.
@@ -33,7 +33,7 @@ func NewClient(tlsConf *tls.Config) Client {
//
// This method will not automatically follow redirects or cache permanent failures or
// redirects.
-func (client Client) RoundTrip(request *gus.Request) (*gus.Response, error) {
+func (client Client) RoundTrip(request *sr.Request) (*sr.Response, error) {
if request.Scheme != "gemini" && request.Scheme != "" {
return nil, errors.New("non-gemini protocols not supported")
}
diff --git a/gemini/gemtext/fuzz_test.go b/gemini/gemtext/fuzz_test.go
index f5435c1..f8d7e28 100644
--- a/gemini/gemtext/fuzz_test.go
+++ b/gemini/gemtext/fuzz_test.go
@@ -4,7 +4,7 @@ import (
"bytes"
"testing"
- "tildegit.org/tjp/gus/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
)
func FuzzParse(f *testing.F) {
diff --git a/gemini/gemtext/htmlconv/convert.go b/gemini/gemtext/htmlconv/convert.go
index 407eb43..101d89a 100644
--- a/gemini/gemtext/htmlconv/convert.go
+++ b/gemini/gemtext/htmlconv/convert.go
@@ -4,8 +4,8 @@ import (
"html/template"
"io"
- "tildegit.org/tjp/gus/gemini/gemtext"
- "tildegit.org/tjp/gus/gemini/gemtext/internal"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext/internal"
)
// Convert writes markdown to a writer from the provided gemtext document.
diff --git a/gemini/gemtext/htmlconv/convert_test.go b/gemini/gemtext/htmlconv/convert_test.go
index 6e8bfcd..36fb6ed 100644
--- a/gemini/gemtext/htmlconv/convert_test.go
+++ b/gemini/gemtext/htmlconv/convert_test.go
@@ -7,8 +7,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "tildegit.org/tjp/gus/gemini/gemtext"
- "tildegit.org/tjp/gus/gemini/gemtext/htmlconv"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext/htmlconv"
)
var gmiDoc = `
diff --git a/gemini/gemtext/internal/templates.go b/gemini/gemtext/internal/templates.go
index 08bc66c..3ca7224 100644
--- a/gemini/gemtext/internal/templates.go
+++ b/gemini/gemtext/internal/templates.go
@@ -5,7 +5,7 @@ import (
"net/url"
"text/template"
- "tildegit.org/tjp/gus/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
)
var Renderers = map[gemtext.LineType]string{
diff --git a/gemini/gemtext/mdconv/convert.go b/gemini/gemtext/mdconv/convert.go
index c2f434d..3416671 100644
--- a/gemini/gemtext/mdconv/convert.go
+++ b/gemini/gemtext/mdconv/convert.go
@@ -4,8 +4,8 @@ import (
"io"
"text/template"
- "tildegit.org/tjp/gus/gemini/gemtext"
- "tildegit.org/tjp/gus/gemini/gemtext/internal"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext/internal"
)
// Convert writes markdown to a writer from the provided gemtext document.
diff --git a/gemini/gemtext/mdconv/convert_test.go b/gemini/gemtext/mdconv/convert_test.go
index c8fd53c..9ada8b4 100644
--- a/gemini/gemtext/mdconv/convert_test.go
+++ b/gemini/gemtext/mdconv/convert_test.go
@@ -8,8 +8,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "tildegit.org/tjp/gus/gemini/gemtext"
- "tildegit.org/tjp/gus/gemini/gemtext/mdconv"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext/mdconv"
)
var gmiDoc = `
diff --git a/gemini/gemtext/parse_line_test.go b/gemini/gemtext/parse_line_test.go
index 82b0c28..9073df0 100644
--- a/gemini/gemtext/parse_line_test.go
+++ b/gemini/gemtext/parse_line_test.go
@@ -3,7 +3,7 @@ package gemtext_test
import (
"testing"
- "tildegit.org/tjp/gus/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
)
func TestParseLinkLine(t *testing.T) {
diff --git a/gemini/gemtext/parse_test.go b/gemini/gemtext/parse_test.go
index 6b35431..90d2c75 100644
--- a/gemini/gemtext/parse_test.go
+++ b/gemini/gemtext/parse_test.go
@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "tildegit.org/tjp/gus/gemini/gemtext"
+ "tildegit.org/tjp/sliderule/gemini/gemtext"
)
func TestParse(t *testing.T) {
diff --git a/gemini/request.go b/gemini/request.go
index 5220952..51aaae5 100644
--- a/gemini/request.go
+++ b/gemini/request.go
@@ -6,7 +6,7 @@ import (
"io"
"net/url"
- "tildegit.org/tjp/gus"
+ sr "tildegit.org/tjp/sliderule"
)
// InvalidRequestLineEnding indicates that a gemini request didn't end with "\r\n".
@@ -15,7 +15,7 @@ var InvalidRequestLineEnding = errors.New("invalid request line ending")
// ParseRequest parses a single gemini request from a reader.
//
// If the reader argument is a *bufio.Reader, it will only read a single line from it.
-func ParseRequest(rdr io.Reader) (*gus.Request, error) {
+func ParseRequest(rdr io.Reader) (*sr.Request, error) {
bufrdr, ok := rdr.(*bufio.Reader)
if !ok {
bufrdr = bufio.NewReader(rdr)
@@ -39,5 +39,19 @@ func ParseRequest(rdr io.Reader) (*gus.Request, error) {
u.Scheme = "gemini"
}
- return &gus.Request{URL: u}, nil
+ return &sr.Request{URL: u}, nil
+}
+
+// GetTitanRequestBody fetches the request body from a titan request.
+//
+// It returns nil if the argument is not a titan request or it otherwise
+// does not have a request body set.
+func GetTitanRequestBody(request *sr.Request) io.Reader {
+ if request.Scheme != "titan" {
+ return nil
+ }
+ if rdr, ok := request.Meta.(io.Reader); ok {
+ return rdr
+ }
+ return nil
}
diff --git a/gemini/request_test.go b/gemini/request_test.go
index 1da24f7..a2b1830 100644
--- a/gemini/request_test.go
+++ b/gemini/request_test.go
@@ -4,7 +4,7 @@ import (
"bytes"
"testing"
- "tildegit.org/tjp/gus/gemini"
+ "tildegit.org/tjp/sliderule/gemini"
)
func TestParseRequest(t *testing.T) {
diff --git a/gemini/response.go b/gemini/response.go
index b8797da..9d4ede1 100644
--- a/gemini/response.go
+++ b/gemini/response.go
@@ -8,7 +8,7 @@ import (
"strconv"
"sync"
- "tildegit.org/tjp/gus"
+ sr "tildegit.org/tjp/sliderule"
)
// ResponseCategory represents the various types of gemini responses.
@@ -43,32 +43,32 @@ const (
ResponseCategoryCertificateRequired
)
-func ResponseCategoryForStatus(status gus.Status) ResponseCategory {
+func ResponseCategoryForStatus(status sr.Status) ResponseCategory {
return ResponseCategory(status / 10)
}
const (
// StatusInput indicates a required query parameter at the requested URL.
- StatusInput gus.Status = gus.Status(ResponseCategoryInput) + iota
+ StatusInput sr.Status = sr.Status(ResponseCategoryInput) + iota
// StatusSensitiveInput indicates a sensitive query parameter is required.
StatusSensitiveInput
)
const (
// StatusSuccess is a successful response.
- StatusSuccess = gus.Status(ResponseCategorySuccess) + iota
+ StatusSuccess = sr.Status(ResponseCategorySuccess) + iota
)
const (
// StatusTemporaryRedirect indicates a temporary redirect to another URL.
- StatusTemporaryRedirect = gus.Status(ResponseCategoryRedirect) + iota
+ StatusTemporaryRedirect = sr.Status(ResponseCategoryRedirect) + iota
// StatusPermanentRedirect indicates that the resource should always be requested at the new URL.
StatusPermanentRedirect
)
const (
// StatusTemporaryFailure indicates that the request failed and there is no response body.
- StatusTemporaryFailure = gus.Status(ResponseCategoryTemporaryFailure) + iota
+ StatusTemporaryFailure = sr.Status(ResponseCategoryTemporaryFailure) + iota
// StatusServerUnavailable occurs when the server is unavailable due to overload or maintenance.
StatusServerUnavailable
// StatusCGIError is the result of a failure of a CGI script.
@@ -84,7 +84,7 @@ const (
const (
// StatusPermanentFailure is a server failure which should be expected to continue indefinitely.
- StatusPermanentFailure = gus.Status(ResponseCategoryPermanentFailure) + iota
+ StatusPermanentFailure = sr.Status(ResponseCategoryPermanentFailure) + iota
// StatusNotFound means the resource doesn't exist but it may in the future.
StatusNotFound
// StatusGone occurs when a resource will not be available any longer.
@@ -92,12 +92,12 @@ const (
// StatusProxyRequestRefused means the server is unwilling to act as a proxy for the resource.
StatusProxyRequestRefused
// StatusBadRequest indicates that the request was malformed somehow.
- StatusBadRequest = gus.Status(ResponseCategoryPermanentFailure) + 9
+ StatusBadRequest = sr.Status(ResponseCategoryPermanentFailure) + 9
)
const (
// StatusClientCertificateRequired is returned when a certificate was required but not provided.
- StatusClientCertificateRequired = gus.Status(ResponseCategoryCertificateRequired) + iota
+ StatusClientCertificateRequired = sr.Status(ResponseCategoryCertificateRequired) + iota
// StatusCertificateNotAuthorized means the certificate doesn't grant access to the requested resource.
StatusCertificateNotAuthorized
// StatusCertificateNotValid means the provided client certificate is invalid.
@@ -105,24 +105,24 @@ const (
)
// Input builds an input-prompting response.
-func Input(prompt string) *gus.Response {
- return &gus.Response{
+func Input(prompt string) *sr.Response {
+ return &sr.Response{
Status: StatusInput,
Meta: prompt,
}
}
// SensitiveInput builds a password-prompting response.
-func SensitiveInput(prompt string) *gus.Response {
- return &gus.Response{
+func SensitiveInput(prompt string) *sr.Response {
+ return &sr.Response{
Status: StatusSensitiveInput,
Meta: prompt,
}
}
// Success builds a success response with resource body.
-func Success(mediatype string, body io.Reader) *gus.Response {
- return &gus.Response{
+func Success(mediatype string, body io.Reader) *sr.Response {
+ return &sr.Response{
Status: StatusSuccess,
Meta: mediatype,
Body: body,
@@ -130,120 +130,120 @@ func Success(mediatype string, body io.Reader) *gus.Response {
}
// Redirect builds a redirect response.
-func Redirect(url string) *gus.Response {
- return &gus.Response{
+func Redirect(url string) *sr.Response {
+ return &sr.Response{
Status: StatusTemporaryRedirect,
Meta: url,
}
}
// PermanentRedirect builds a response with a permanent redirect.
-func PermanentRedirect(url string) *gus.Response {
- return &gus.Response{
+func PermanentRedirect(url string) *sr.Response {
+ return &sr.Response{
Status: StatusPermanentRedirect,
Meta: url,
}
}
// Failure builds a temporary failure response from an error.
-func Failure(err error) *gus.Response {
- return &gus.Response{
+func Failure(err error) *sr.Response {
+ return &sr.Response{
Status: StatusTemporaryFailure,
Meta: err.Error(),
}
}
// Unavailable build a "server unavailable" response.
-func Unavailable(msg string) *gus.Response {
- return &gus.Response{
+func Unavailable(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusServerUnavailable,
Meta: msg,
}
}
// CGIError builds a "cgi error" response.
-func CGIError(err string) *gus.Response {
- return &gus.Response{
+func CGIError(err string) *sr.Response {
+ return &sr.Response{
Status: StatusCGIError,
Meta: err,
}
}
// ProxyError builds a proxy error response.
-func ProxyError(msg string) *gus.Response {
- return &gus.Response{
+func ProxyError(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusProxyError,
Meta: msg,
}
}
// SlowDown builds a "slow down" response with the number of seconds until the resource is available.
-func SlowDown(seconds int) *gus.Response {
- return &gus.Response{
+func SlowDown(seconds int) *sr.Response {
+ return &sr.Response{
Status: StatusSlowDown,
Meta: strconv.Itoa(seconds),
}
}
// PermanentFailure builds a "permanent failure" from an error.
-func PermanentFailure(err error) *gus.Response {
- return &gus.Response{
+func PermanentFailure(err error) *sr.Response {
+ return &sr.Response{
Status: StatusPermanentFailure,
Meta: err.Error(),
}
}
// NotFound builds a "resource not found" response.
-func NotFound(msg string) *gus.Response {
- return &gus.Response{
+func NotFound(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusNotFound,
Meta: msg,
}
}
// Gone builds a "resource gone" response.
-func Gone(msg string) *gus.Response {
- return &gus.Response{
+func Gone(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusGone,
Meta: msg,
}
}
// RefuseProxy builds a "proxy request refused" response.
-func RefuseProxy(msg string) *gus.Response {
- return &gus.Response{
+func RefuseProxy(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusProxyRequestRefused,
Meta: msg,
}
}
// BadRequest builds a "bad request" response.
-func BadRequest(msg string) *gus.Response {
- return &gus.Response{
+func BadRequest(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusBadRequest,
Meta: msg,
}
}
// RequireCert builds a "client certificate required" response.
-func RequireCert(msg string) *gus.Response {
- return &gus.Response{
+func RequireCert(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusClientCertificateRequired,
Meta: msg,
}
}
// CertAuthFailure builds a "certificate not authorized" response.
-func CertAuthFailure(msg string) *gus.Response {
- return &gus.Response{
+func CertAuthFailure(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusCertificateNotAuthorized,
Meta: msg,
}
}
// CertInvalid builds a "client certificate not valid" response.
-func CertInvalid(msg string) *gus.Response {
- return &gus.Response{
+func CertInvalid(msg string) *sr.Response {
+ return &sr.Response{
Status: StatusCertificateNotValid,
Meta: msg,
}
@@ -258,7 +258,7 @@ var InvalidResponseHeaderLine = errors.New("Invalid response header line.")
// ParseResponse parses a complete gemini response from a reader.
//
// The reader must contain only one gemini response.
-func ParseResponse(rdr io.Reader) (*gus.Response, error) {
+func ParseResponse(rdr io.Reader) (*sr.Response, error) {
bufrdr := bufio.NewReader(rdr)
hdrLine, err := bufrdr.ReadBytes('\n')
@@ -278,14 +278,14 @@ func ParseResponse(rdr io.Reader) (*gus.Response, error) {
return nil, InvalidResponseHeaderLine
}
- return &gus.Response{
- Status: gus.Status(status),
+ return &sr.Response{
+ Status: sr.Status(status),
Meta: string(hdrLine[3:]),
Body: bufrdr,
}, nil
}
-func NewResponseReader(response *gus.Response) gus.ResponseReader {
+func NewResponseReader(response *sr.Response) sr.ResponseReader {
return &responseReader{
Response: response,
once: &sync.Once{},
@@ -293,7 +293,7 @@ func NewResponseReader(response *gus.Response) gus.ResponseReader {
}
type responseReader struct {
- *gus.Response
+ *sr.Response
reader io.Reader
once *sync.Once
}
diff --git a/gemini/response_test.go b/gemini/response_test.go
index 9287d71..00166dd 100644
--- a/gemini/response_test.go
+++ b/gemini/response_test.go
@@ -6,15 +6,15 @@ import (
"io"
"testing"
- "tildegit.org/tjp/gus"
- "tildegit.org/tjp/gus/gemini"
+ sr "tildegit.org/tjp/sliderule"
+ "tildegit.org/tjp/sliderule/gemini"
)
func TestBuildResponses(t *testing.T) {
table := []struct {
name string
- response *gus.Response
- status gus.Status
+ response *sr.Response
+ status sr.Status
meta string
body string
}{
@@ -154,7 +154,7 @@ func TestBuildResponses(t *testing.T) {
func TestParseResponses(t *testing.T) {
table := []struct {
input string
- status gus.Status
+ status sr.Status
meta string
body string
err error
@@ -233,7 +233,7 @@ func TestParseResponses(t *testing.T) {
func TestResponseClose(t *testing.T) {
body := &rdCloser{Buffer: bytes.NewBufferString("the body here")}
- resp := &gus.Response{
+ resp := &sr.Response{
Status: gemini.StatusSuccess,
Meta: "text/gemini",
Body: body,
@@ -247,7 +247,7 @@ func TestResponseClose(t *testing.T) {
t.Error("response body was not closed by response.Close()")
}
- resp = &gus.Response{
+ resp = &sr.Response{
Status: gemini.StatusInput,
Meta: "give me more",
}
@@ -270,8 +270,8 @@ func (rc *rdCloser) Close() error {
func TestResponseWriteTo(t *testing.T) {
// invariant under test: WriteTo() sends the same bytes as Read()
- clone := func(resp *gus.Response) *gus.Response {
- other := &gus.Response{
+ clone := func(resp *sr.Response) *sr.Response {
+ other := &sr.Response{
Status: resp.Status,
Meta: resp.Meta,
}
@@ -297,7 +297,7 @@ func TestResponseWriteTo(t *testing.T) {
table := []struct {
name string
- response *gus.Response
+ response *sr.Response
}{
{
name: "simple success",
diff --git a/gemini/roundtrip_test.go b/gemini/roundtrip_test.go
index e8d2b48..67b1ae0 100644
--- a/gemini/roundtrip_test.go
+++ b/gemini/roundtrip_test.go
@@ -12,15 +12,15 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "tildegit.org/tjp/gus"
- "tildegit.org/tjp/gus/gemini"
+ sr "tildegit.org/tjp/sliderule"
+ "tildegit.org/tjp/sliderule/gemini"
)
func TestRoundTrip(t *testing.T) {
tlsConf, err := gemini.FileTLS("./testdata/server.crt", "./testdata/server.key")
require.Nil(t, err)
- handler := gus.HandlerFunc(func(ctx context.Context, req *gus.Request) *gus.Response {
+ handler := sr.HandlerFunc(func(ctx context.Context, req *sr.Request) *sr.Response {
return gemini.Success("text/gemini", bytes.NewBufferString("you've found my page"))
})
@@ -36,7 +36,7 @@ func TestRoundTrip(t *testing.T) {
require.Nil(t, err)
cli := gemini.NewClient(testClientTLS())
- response, err := cli.RoundTrip(&gus.Request{URL: u})
+ response, err := cli.RoundTrip(&sr.Request{URL: u})
require.Nil(t, err)
assert.Equal(t, gemini.StatusSuccess, response.Status)
@@ -54,10 +54,10 @@ func TestTitanRequest(t *testing.T) {
require.Nil(t, err)
invoked := false
- handler := gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
+ handler := sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
invoked = true
- body := ctx.Value(gemini.TitanRequestBody)
+ body := gemini.GetTitanRequestBody(request)
if !assert.NotNil(t, body) {
return gemini.Success("", nil)
}
diff --git a/gemini/serve.go b/gemini/serve.go
index 2f93153..01e186f 100644
--- a/gemini/serve.go
+++ b/gemini/serve.go
@@ -11,23 +11,17 @@ import (
"strconv"
"strings"
- "tildegit.org/tjp/gus"
- "tildegit.org/tjp/gus/internal"
- "tildegit.org/tjp/gus/logging"
+ sr "tildegit.org/tjp/sliderule"
+ "tildegit.org/tjp/sliderule/internal"
+ "tildegit.org/tjp/sliderule/logging"
)
type titanRequestBodyKey struct{}
-// TitanRequestBody is the key set in a handler's context for titan requests.
-//
-// When this key is present in the context (request.URL.Scheme will be "titan"), the
-// corresponding value is a *bufio.Reader from which the request body can be read.
-var TitanRequestBody = titanRequestBodyKey{}
-
type server struct {
internal.Server
- handler gus.Handler
+ handler sr.Handler
}
func (s server) Protocol() string { return "GEMINI" }
@@ -38,10 +32,10 @@ func NewServer(
hostname string,
network string,
address string,
- handler gus.Handler,
+ handler sr.Handler,
errorLog logging.Logger,
tlsConfig *tls.Config,
-) (gus.Server, error) {
+) (sr.Server, error) {
s := &server{handler: handler}
if strings.IndexByte(hostname, ':') < 0 {
@@ -62,7 +56,7 @@ func NewServer(
func (s *server) handleConn(conn net.Conn) {
buf := bufio.NewReader(conn)
- var response *gus.Response
+ var response *sr.Response
request, err := ParseRequest(buf)
if err != nil {
response = BadRequest(err.Error())
@@ -79,11 +73,7 @@ func (s *server) handleConn(conn net.Conn) {
if request.Scheme == "titan" {
len, err := sizeParam(request.Path)
if err == nil {
- ctx = context.WithValue(
- ctx,
- TitanRequestBody,
- io.LimitReader(buf, int64(len)),
- )
+ request.Meta = io.LimitReader(buf, int64(len))
}
}
@@ -125,9 +115,9 @@ func sizeParam(path string) (int, error) {
// Optionally, it will also allow through titan:// requests.
//
// Filtered requests will be turned away with a 53 response "proxy request refused".
-func GeminiOnly(allowTitan bool) gus.Middleware {
- return func(inner gus.Handler) gus.Handler {
- return gus.HandlerFunc(func(ctx context.Context, request *gus.Request) *gus.Response {
+func GeminiOnly(allowTitan bool) sr.Middleware {
+ return func(inner sr.Handler) sr.Handler {
+ return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
if request.Scheme == "gemini" || (allowTitan && request.Scheme == "titan") {
return inner.Handle(ctx, request)
}