nrpc

package
v0.0.0-...-c5376a2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 23, 2022 License: MIT Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInvalidLengthNrpc        = fmt.Errorf("proto: negative length found during unmarshaling")
	ErrIntOverflowNrpc          = fmt.Errorf("proto: integer overflow")
	ErrUnexpectedEndOfGroupNrpc = fmt.Errorf("proto: unexpected end of group")
)
View Source
var (
	ErrInvalidLengthNrpcTest        = fmt.Errorf("proto: negative length found during unmarshaling")
	ErrIntOverflowNrpcTest          = fmt.Errorf("proto: integer overflow")
	ErrUnexpectedEndOfGroupNrpcTest = fmt.Errorf("proto: unexpected end of group")
)
View Source
var E_MethodSubject = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.MethodOptions)(nil),
	ExtensionType: (*string)(nil),
	Field:         52000,
	Name:          "nrpc.methodSubject",
	Tag:           "bytes,52000,opt,name=methodSubject",
	Filename:      "nrpc.proto",
}
View Source
var E_MethodSubjectParams = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.MethodOptions)(nil),
	ExtensionType: ([]string)(nil),
	Field:         52001,
	Name:          "nrpc.methodSubjectParams",
	Tag:           "bytes,52001,rep,name=methodSubjectParams",
	Filename:      "nrpc.proto",
}
View Source
var E_MethodSubjectRule = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FileOptions)(nil),
	ExtensionType: (*SubjectRule)(nil),
	Field:         50003,
	Name:          "nrpc.methodSubjectRule",
	Tag:           "varint,50003,opt,name=methodSubjectRule,enum=nrpc.SubjectRule",
	Filename:      "nrpc.proto",
}
View Source
var E_PackageSubject = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FileOptions)(nil),
	ExtensionType: (*string)(nil),
	Field:         50000,
	Name:          "nrpc.packageSubject",
	Tag:           "bytes,50000,opt,name=packageSubject",
	Filename:      "nrpc.proto",
}
View Source
var E_PackageSubjectParams = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FileOptions)(nil),
	ExtensionType: ([]string)(nil),
	Field:         50001,
	Name:          "nrpc.packageSubjectParams",
	Tag:           "bytes,50001,rep,name=packageSubjectParams",
	Filename:      "nrpc.proto",
}
View Source
var E_ServiceSubject = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.ServiceOptions)(nil),
	ExtensionType: (*string)(nil),
	Field:         51000,
	Name:          "nrpc.serviceSubject",
	Tag:           "bytes,51000,opt,name=serviceSubject",
	Filename:      "nrpc.proto",
}
View Source
var E_ServiceSubjectParams = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.ServiceOptions)(nil),
	ExtensionType: ([]string)(nil),
	Field:         51001,
	Name:          "nrpc.serviceSubjectParams",
	Tag:           "bytes,51001,rep,name=serviceSubjectParams",
	Filename:      "nrpc.proto",
}
View Source
var E_ServiceSubjectRule = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.FileOptions)(nil),
	ExtensionType: (*SubjectRule)(nil),
	Field:         50002,
	Name:          "nrpc.serviceSubjectRule",
	Tag:           "varint,50002,opt,name=serviceSubjectRule,enum=nrpc.SubjectRule",
	Filename:      "nrpc.proto",
}
View Source
var E_StreamedReply = &proto.ExtensionDesc{
	ExtendedType:  (*descriptor.MethodOptions)(nil),
	ExtensionType: (*bool)(nil),
	Field:         52002,
	Name:          "nrpc.streamedReply",
	Tag:           "varint,52002,opt,name=streamedReply",
	Filename:      "nrpc.proto",
}
View Source
var ErrCanceled = errors.New("Call canceled")
View Source
var ErrEOS = errors.New("End of stream")
View Source
var ErrStreamInvalidMsgCount = errors.New("Stream reply received an incorrect number of messages")

ErrStreamInvalidMsgCount is when a stream reply gets a wrong number of messages

View Source
var Error_Type_name = map[int32]string{
	0: "CLIENT",
	1: "SERVER",
	3: "EOS",
	4: "SERVERTOOBUSY",
}
View Source
var Error_Type_value = map[string]int32{
	"CLIENT":        0,
	"SERVER":        1,
	"EOS":           3,
	"SERVERTOOBUSY": 4,
}
View Source
var SubjectRule_name = map[int32]string{
	0: "COPY",
	1: "TOLOWER",
}
View Source
var SubjectRule_value = map[string]int32{
	"COPY":    0,
	"TOLOWER": 1,
}

Functions

func Call

func Call(req proto.Message, rep proto.Message, nc NatsConn, subject string, encoding string, timeout time.Duration) error

func Marshal

func Marshal(encoding string, msg proto.Message) ([]byte, error)

func MarshalErrorResponse

func MarshalErrorResponse(encoding string, repErr *Error) ([]byte, error)

func ParseSubject

func ParseSubject(
	packageSubject string, packageParamsCount int,
	serviceSubject string, serviceParamsCount int,
	subject string,
) (packageParams []string, serviceParams []string,
	name string, tail []string, err error,
)

func ParseSubjectTail

func ParseSubjectTail(
	methodParamsCount int,
	tail []string,
) (
	methodParams []string, encoding string, err error,
)

func Publish

func Publish(resp proto.Message, withError *Error, nc NatsConn, subject string, encoding string) error

func Unmarshal

func Unmarshal(encoding string, data []byte, msg proto.Message) error

func UnmarshalResponse

func UnmarshalResponse(encoding string, data []byte, msg proto.Message) error

Types

type ContextKey

type ContextKey int

ContextKey type for storing values into context.Context

const (
	// RequestContextKey is the key for string the request into the context
	RequestContextKey ContextKey = iota
)

type DummyMessage

type DummyMessage struct {
	Foobar string `protobuf:"bytes,1,opt,name=foobar,proto3" json:"foobar,omitempty"`
}

func NewPopulatedDummyMessage

func NewPopulatedDummyMessage(r randyNrpcTest, easy bool) *DummyMessage

func (*DummyMessage) Descriptor

func (*DummyMessage) Descriptor() ([]byte, []int)

func (*DummyMessage) Equal

func (this *DummyMessage) Equal(that interface{}) bool

func (*DummyMessage) GetFoobar

func (m *DummyMessage) GetFoobar() string

func (*DummyMessage) GoString

func (this *DummyMessage) GoString() string

func (*DummyMessage) Marshal

func (m *DummyMessage) Marshal() (dAtA []byte, err error)

func (*DummyMessage) MarshalTo

func (m *DummyMessage) MarshalTo(dAtA []byte) (int, error)

func (*DummyMessage) MarshalToSizedBuffer

func (m *DummyMessage) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*DummyMessage) ProtoMessage

func (*DummyMessage) ProtoMessage()

func (*DummyMessage) Reset

func (m *DummyMessage) Reset()

func (*DummyMessage) Size

func (m *DummyMessage) Size() (n int)

func (*DummyMessage) String

func (this *DummyMessage) String() string

func (*DummyMessage) Unmarshal

func (m *DummyMessage) Unmarshal(dAtA []byte) error

func (*DummyMessage) VerboseEqual

func (this *DummyMessage) VerboseEqual(that interface{}) error

func (*DummyMessage) XXX_DiscardUnknown

func (m *DummyMessage) XXX_DiscardUnknown()

func (*DummyMessage) XXX_Marshal

func (m *DummyMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*DummyMessage) XXX_Merge

func (m *DummyMessage) XXX_Merge(src proto.Message)

func (*DummyMessage) XXX_Size

func (m *DummyMessage) XXX_Size() int

func (*DummyMessage) XXX_Unmarshal

func (m *DummyMessage) XXX_Unmarshal(b []byte) error

type Error

type Error struct {
	Type     Error_Type `protobuf:"varint,1,opt,name=type,proto3,enum=nrpc.Error_Type" json:"type,omitempty"`
	Message  string     `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"`
	MsgCount uint32     `protobuf:"varint,3,opt,name=msgCount,proto3" json:"msgCount,omitempty"`
}

func CaptureErrors

func CaptureErrors(fn func() (proto.Message, error)) (msg proto.Message, replyError *Error)

CaptureErrors runs a handler and convert error and panics into proper Error

func NewPopulatedError

func NewPopulatedError(r randyNrpc, easy bool) *Error

func (*Error) Descriptor

func (*Error) Descriptor() ([]byte, []int)

func (*Error) Equal

func (this *Error) Equal(that interface{}) bool

func (*Error) Error

func (e *Error) Error() string

func (*Error) GetMessage

func (m *Error) GetMessage() string

func (*Error) GetMsgCount

func (m *Error) GetMsgCount() uint32

func (*Error) GetType

func (m *Error) GetType() Error_Type

func (*Error) GoString

func (this *Error) GoString() string

func (*Error) Marshal

func (m *Error) Marshal() (dAtA []byte, err error)

func (*Error) MarshalTo

func (m *Error) MarshalTo(dAtA []byte) (int, error)

func (*Error) MarshalToSizedBuffer

func (m *Error) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*Error) ProtoMessage

func (*Error) ProtoMessage()

func (*Error) Reset

func (m *Error) Reset()

func (*Error) Size

func (m *Error) Size() (n int)

func (*Error) String

func (this *Error) String() string

func (*Error) Unmarshal

func (m *Error) Unmarshal(dAtA []byte) error

func (*Error) VerboseEqual

func (this *Error) VerboseEqual(that interface{}) error

func (*Error) XXX_DiscardUnknown

func (m *Error) XXX_DiscardUnknown()

func (*Error) XXX_Marshal

func (m *Error) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*Error) XXX_Merge

func (m *Error) XXX_Merge(src proto.Message)

func (*Error) XXX_Size

func (m *Error) XXX_Size() int

func (*Error) XXX_Unmarshal

func (m *Error) XXX_Unmarshal(b []byte) error

type Error_Type

type Error_Type int32
const (
	Error_CLIENT        Error_Type = 0
	Error_SERVER        Error_Type = 1
	Error_EOS           Error_Type = 3
	Error_SERVERTOOBUSY Error_Type = 4
)

func (Error_Type) EnumDescriptor

func (Error_Type) EnumDescriptor() ([]byte, []int)

func (Error_Type) String

func (x Error_Type) String() string

type H

type H interface {
	Subject() string
	Handler(msg *nats.Msg)
	SetNats(nc *nats.Conn)
	SetMiddleware(ms []Middleware)
}

type Handler

type Handler func(ctx context.Context) (proto.Message, error)

Handler defines the handler invoked by Middleware.

type HeartBeat

type HeartBeat struct {
	Lastbeat bool `protobuf:"varint,1,opt,name=lastbeat,proto3" json:"lastbeat,omitempty"`
}

func NewPopulatedHeartBeat

func NewPopulatedHeartBeat(r randyNrpc, easy bool) *HeartBeat

func (*HeartBeat) Descriptor

func (*HeartBeat) Descriptor() ([]byte, []int)

func (*HeartBeat) Equal

func (this *HeartBeat) Equal(that interface{}) bool

func (*HeartBeat) GetLastbeat

func (m *HeartBeat) GetLastbeat() bool

func (*HeartBeat) GoString

func (this *HeartBeat) GoString() string

func (*HeartBeat) Marshal

func (m *HeartBeat) Marshal() (dAtA []byte, err error)

func (*HeartBeat) MarshalTo

func (m *HeartBeat) MarshalTo(dAtA []byte) (int, error)

func (*HeartBeat) MarshalToSizedBuffer

func (m *HeartBeat) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*HeartBeat) ProtoMessage

func (*HeartBeat) ProtoMessage()

func (*HeartBeat) Reset

func (m *HeartBeat) Reset()

func (*HeartBeat) Size

func (m *HeartBeat) Size() (n int)

func (*HeartBeat) String

func (this *HeartBeat) String() string

func (*HeartBeat) Unmarshal

func (m *HeartBeat) Unmarshal(dAtA []byte) error

func (*HeartBeat) VerboseEqual

func (this *HeartBeat) VerboseEqual(that interface{}) error

func (*HeartBeat) XXX_DiscardUnknown

func (m *HeartBeat) XXX_DiscardUnknown()

func (*HeartBeat) XXX_Marshal

func (m *HeartBeat) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*HeartBeat) XXX_Merge

func (m *HeartBeat) XXX_Merge(src proto.Message)

func (*HeartBeat) XXX_Size

func (m *HeartBeat) XXX_Size() int

func (*HeartBeat) XXX_Unmarshal

func (m *HeartBeat) XXX_Unmarshal(b []byte) error

type KeepStreamAlive

type KeepStreamAlive struct {
	// contains filtered or unexported fields
}

func NewKeepStreamAlive

func NewKeepStreamAlive(nc NatsConn, subject string, encoding string, onError func()) *KeepStreamAlive

func (*KeepStreamAlive) Stop

func (k *KeepStreamAlive) Stop()

type Middleware

type Middleware func(Handler) Handler

Middleware is HTTP/gRPC transport middleware.

func Chain

func Chain(m ...Middleware) Middleware

Chain returns a Middleware that specifies the chained handler for endpoint.

type NatsConn

type NatsConn interface {
	Publish(subj string, data []byte) error
	PublishRequest(subj, reply string, data []byte) error
	Request(subj string, data []byte, timeout time.Duration) (*nats.Msg, error)

	ChanSubscribe(subj string, ch chan *nats.Msg) (*nats.Subscription, error)
	Subscribe(subj string, handler nats.MsgHandler) (*nats.Subscription, error)
	SubscribeSync(subj string) (*nats.Subscription, error)
}

type NoReply

type NoReply struct {
}

func NewPopulatedNoReply

func NewPopulatedNoReply(r randyNrpc, easy bool) *NoReply

func (*NoReply) Descriptor

func (*NoReply) Descriptor() ([]byte, []int)

func (*NoReply) Equal

func (this *NoReply) Equal(that interface{}) bool

func (*NoReply) GoString

func (this *NoReply) GoString() string

func (*NoReply) Marshal

func (m *NoReply) Marshal() (dAtA []byte, err error)

func (*NoReply) MarshalTo

func (m *NoReply) MarshalTo(dAtA []byte) (int, error)

func (*NoReply) MarshalToSizedBuffer

func (m *NoReply) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*NoReply) ProtoMessage

func (*NoReply) ProtoMessage()

func (*NoReply) Reset

func (m *NoReply) Reset()

func (*NoReply) Size

func (m *NoReply) Size() (n int)

func (*NoReply) String

func (this *NoReply) String() string

func (*NoReply) Unmarshal

func (m *NoReply) Unmarshal(dAtA []byte) error

func (*NoReply) VerboseEqual

func (this *NoReply) VerboseEqual(that interface{}) error

func (*NoReply) XXX_DiscardUnknown

func (m *NoReply) XXX_DiscardUnknown()

func (*NoReply) XXX_Marshal

func (m *NoReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*NoReply) XXX_Merge

func (m *NoReply) XXX_Merge(src proto.Message)

func (*NoReply) XXX_Size

func (m *NoReply) XXX_Size() int

func (*NoReply) XXX_Unmarshal

func (m *NoReply) XXX_Unmarshal(b []byte) error

type NoRequest

type NoRequest struct {
}

func NewPopulatedNoRequest

func NewPopulatedNoRequest(r randyNrpc, easy bool) *NoRequest

func (*NoRequest) Descriptor

func (*NoRequest) Descriptor() ([]byte, []int)

func (*NoRequest) Equal

func (this *NoRequest) Equal(that interface{}) bool

func (*NoRequest) GoString

func (this *NoRequest) GoString() string

func (*NoRequest) Marshal

func (m *NoRequest) Marshal() (dAtA []byte, err error)

func (*NoRequest) MarshalTo

func (m *NoRequest) MarshalTo(dAtA []byte) (int, error)

func (*NoRequest) MarshalToSizedBuffer

func (m *NoRequest) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*NoRequest) ProtoMessage

func (*NoRequest) ProtoMessage()

func (*NoRequest) Reset

func (m *NoRequest) Reset()

func (*NoRequest) Size

func (m *NoRequest) Size() (n int)

func (*NoRequest) String

func (this *NoRequest) String() string

func (*NoRequest) Unmarshal

func (m *NoRequest) Unmarshal(dAtA []byte) error

func (*NoRequest) VerboseEqual

func (this *NoRequest) VerboseEqual(that interface{}) error

func (*NoRequest) XXX_DiscardUnknown

func (m *NoRequest) XXX_DiscardUnknown()

func (*NoRequest) XXX_Marshal

func (m *NoRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*NoRequest) XXX_Merge

func (m *NoRequest) XXX_Merge(src proto.Message)

func (*NoRequest) XXX_Size

func (m *NoRequest) XXX_Size() int

func (*NoRequest) XXX_Unmarshal

func (m *NoRequest) XXX_Unmarshal(b []byte) error

type ReplyInboxMaker

type ReplyInboxMaker func(NatsConn) string

ReplyInboxMaker returns a new inbox subject for a given nats connection.

var GetReplyInbox ReplyInboxMaker = func(NatsConn) string {
	return nats.NewInbox()
}

GetReplyInbox is used by StreamCall to get a inbox subject It can be changed by a client lib that needs custom inbox subjects

type Request

type Request struct {
	Context context.Context
	Conn    NatsConn

	KeepStreamAlive *KeepStreamAlive
	StreamContext   context.Context
	StreamCancel    func()
	StreamMsgCount  uint32

	Subject     string
	MethodName  string
	SubjectTail []string

	CreatedAt time.Time
	StartedAt time.Time

	Encoding     string
	NoReply      bool
	ReplySubject string

	PackageParams map[string]string
	ServiceParams map[string]string

	AfterReply func(r *Request, success bool, replySuccess bool)

	Handler func(context.Context) (proto.Message, error)
	Ms      []Middleware
	// contains filtered or unexported fields
}

Request is a server-side incoming request

func GetRequest

func GetRequest(ctx context.Context) *Request

GetRequest returns the Request associated with a context, or nil if absent

func NewRequest

func NewRequest(ctx context.Context, conn NatsConn, subject string, replySubject string, ms []Middleware) *Request

NewRequest creates a Request instance

func (*Request) Elapsed

func (r *Request) Elapsed() time.Duration

Elapsed duration since request was started

func (*Request) EnableStreamedReply

func (r *Request) EnableStreamedReply()

EnableStreamedReply enables the streamed reply mode

func (*Request) PackageParam

func (r *Request) PackageParam(key string) string

PackageParam returns a package parameter value, or "" if absent

func (*Request) Run

func (r *Request) Run() (msg proto.Message, replyError *Error)

Run the handler and capture any error. Returns the response or the error that should be returned to the caller

func (*Request) RunAndReply

func (r *Request) RunAndReply()

RunAndReply calls Run() and send the reply back to the caller

func (*Request) SendErrorTooBusy

func (r *Request) SendErrorTooBusy(msg string) error

SendErrorTooBusy cancels the request with a 'SERVERTOOBUSY' error

func (*Request) SendReply

func (r *Request) SendReply(resp proto.Message, withError *Error) error

SendReply sends a reply to the caller

func (*Request) SendStreamReply

func (r *Request) SendStreamReply(msg proto.Message)

SendStreamReply send a reply a part of a stream

func (*Request) ServiceParam

func (r *Request) ServiceParam(key string) string

ServiceParam returns a package parameter value, or "" if absent

func (*Request) SetPackageParam

func (r *Request) SetPackageParam(key, value string)

SetPackageParam sets a package param value

func (*Request) SetServiceParam

func (r *Request) SetServiceParam(key, value string)

SetServiceParam sets a service param value

func (*Request) StreamedReply

func (r *Request) StreamedReply() bool

StreamedReply returns true if the request reply is streamed

type RequestHandler

type RequestHandler func(context.Context) (proto.Message, error)

type StreamCallSubscription

type StreamCallSubscription struct {
	// contains filtered or unexported fields
}

func NewStreamCallSubscription

func NewStreamCallSubscription(
	ctx context.Context, nc NatsConn, encoding string, subject string,
	timeout time.Duration,
) (*StreamCallSubscription, error)

func StreamCall

func StreamCall(ctx context.Context, nc NatsConn, subject string, req proto.Message, encoding string, timeout time.Duration) (*StreamCallSubscription, error)

func (*StreamCallSubscription) Next

func (sub *StreamCallSubscription) Next(rep proto.Message) error

type SubjectRule

type SubjectRule int32
const (
	SubjectRule_COPY    SubjectRule = 0
	SubjectRule_TOLOWER SubjectRule = 1
)

func (SubjectRule) EnumDescriptor

func (SubjectRule) EnumDescriptor() ([]byte, []int)

func (SubjectRule) String

func (x SubjectRule) String() string

type Void

type Void struct {
}

func NewPopulatedVoid

func NewPopulatedVoid(r randyNrpc, easy bool) *Void

func (*Void) Descriptor

func (*Void) Descriptor() ([]byte, []int)

func (*Void) Equal

func (this *Void) Equal(that interface{}) bool

func (*Void) GoString

func (this *Void) GoString() string

func (*Void) Marshal

func (m *Void) Marshal() (dAtA []byte, err error)

func (*Void) MarshalTo

func (m *Void) MarshalTo(dAtA []byte) (int, error)

func (*Void) MarshalToSizedBuffer

func (m *Void) MarshalToSizedBuffer(dAtA []byte) (int, error)

func (*Void) ProtoMessage

func (*Void) ProtoMessage()

func (*Void) Reset

func (m *Void) Reset()

func (*Void) Size

func (m *Void) Size() (n int)

func (*Void) String

func (this *Void) String() string

func (*Void) Unmarshal

func (m *Void) Unmarshal(dAtA []byte) error

func (*Void) VerboseEqual

func (this *Void) VerboseEqual(that interface{}) error

func (*Void) XXX_DiscardUnknown

func (m *Void) XXX_DiscardUnknown()

func (*Void) XXX_Marshal

func (m *Void) XXX_Marshal(b []byte, deterministic bool) ([]byte, error)

func (*Void) XXX_Merge

func (m *Void) XXX_Merge(src proto.Message)

func (*Void) XXX_Size

func (m *Void) XXX_Size() int

func (*Void) XXX_Unmarshal

func (m *Void) XXX_Unmarshal(b []byte) error

type WorkerPool

type WorkerPool struct {
	Context context.Context
	// contains filtered or unexported fields
}

WorkerPool is a pool of workers

func NewWorkerPool

func NewWorkerPool(
	size uint,
	maxPending uint,
	maxPendingDuration time.Duration,
) *WorkerPool

NewWorkerPool creates a pool of workers

func (*WorkerPool) Close

func (pool *WorkerPool) Close(timeout time.Duration)

Close stops all the workers and wait for their completion If the workers do not stop before the timeout, their context is canceled Will never return if a request ignores the context

func (*WorkerPool) QueueRequest

func (pool *WorkerPool) QueueRequest(request *Request) error

QueueRequest adds a request to the queue Send a SERVERTOOBUSY error to the client if the queue is full

func (*WorkerPool) SetMaxPending

func (pool *WorkerPool) SetMaxPending(value uint)

SetMaxPending changes the queue size

func (*WorkerPool) SetMaxPendingDuration

func (pool *WorkerPool) SetMaxPendingDuration(value time.Duration)

SetMaxPendingDuration changes the max pending delay

func (*WorkerPool) SetSize

func (pool *WorkerPool) SetSize(size uint)

SetSize changes the number of workers

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL