mirror of
https://github.com/soypat/lneto.git
synced 2026-08-21 06:59:04 +00:00
explicit naming of headerCapacityKV value in kvBuffer.Reset
This commit is contained in:
@@ -80,7 +80,7 @@ func ExampleMuxSlice_query_forms_multipart() {
|
|||||||
// Request Body Form. The form owns the memory: hand it a buffer and
|
// Request Body Form. The form owns the memory: hand it a buffer and
|
||||||
// forbid growth to bound what a request may spend.
|
// forbid growth to bound what a request may spend.
|
||||||
var form httpraw.Form
|
var form httpraw.Form
|
||||||
form.Reset(make([]byte, 0, 1024), 8)
|
form.Reset(make([]byte, 0, 1024), 8) // Room for 8 pairs.
|
||||||
form.EnableBufferGrowth(false)
|
form.EnableBufferGrowth(false)
|
||||||
err := ex.RequestParseForm(&form, false, false)
|
err := ex.RequestParseForm(&form, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ func nopBackoff(consecutiveBackoffs uint) time.Duration { return lneto.BackoffFl
|
|||||||
// where it says so.
|
// where it says so.
|
||||||
const defaultNumHeaderKVCap = 32
|
const defaultNumHeaderKVCap = 32
|
||||||
|
|
||||||
|
// defaultKVCap is the pair table size tests hand to [httpraw.Form.Reset], a
|
||||||
|
// bounded form needing room for the pairs it parses. See [httpraw.Form.Reset].
|
||||||
|
const defaultKVCap = 8
|
||||||
|
|
||||||
// newExchange returns an Exchange acquired on conn, ready to serve a request.
|
// newExchange returns an Exchange acquired on conn, ready to serve a request.
|
||||||
func newExchange(t *testing.T, conn conn, cfg ExchangeConfig) *Exchange {
|
func newExchange(t *testing.T, conn conn, cfg ExchangeConfig) *Exchange {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -872,7 +876,7 @@ func TestExchangeRequestParseForm(t *testing.T) {
|
|||||||
// The form owns the memory: bufSize bounds it here, growth off so an
|
// The form owns the memory: bufSize bounds it here, growth off so an
|
||||||
// oversized body is reported rather than allocated for.
|
// oversized body is reported rather than allocated for.
|
||||||
var form httpraw.Form
|
var form httpraw.Form
|
||||||
form.Reset(make([]byte, 0, bufSize), 8)
|
form.Reset(make([]byte, 0, bufSize), defaultKVCap)
|
||||||
form.EnableBufferGrowth(false)
|
form.EnableBufferGrowth(false)
|
||||||
var gotErr error
|
var gotErr error
|
||||||
var sm MuxSlice
|
var sm MuxSlice
|
||||||
@@ -1482,40 +1486,6 @@ func TestExchangeRequestParseFormFoldedTransferEncoding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// StageHeaderInt defaults to base 10, the only base an HTTP field value uses,
|
|
||||||
// so callers do not repeat it at every site.
|
|
||||||
func TestExchangeStageHeaderIntDefaultsBase10(t *testing.T) {
|
|
||||||
conn := newConn("")
|
|
||||||
exch := newExchange(t, conn, ExchangeConfig{RawBuf: make([]byte, 256), RequestBufferLim: 128})
|
|
||||||
if !exch.StageHeaderInt("Content-Length", 1234567890) {
|
|
||||||
t.Fatal("want field staged")
|
|
||||||
}
|
|
||||||
exch.WriteHeader(200)
|
|
||||||
const want = "HTTP/1.1 200 OK\r\nContent-Length:1234567890\r\n\r\n"
|
|
||||||
if got := conn.ViewWritten(); got != want {
|
|
||||||
t.Errorf("want %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StageHeaderBytes stages a value already held as bytes without the caller
|
|
||||||
// converting it to a string first.
|
|
||||||
func TestExchangeStageHeaderBytes(t *testing.T) {
|
|
||||||
conn := newConn("")
|
|
||||||
exch := newExchange(t, conn, ExchangeConfig{RawBuf: make([]byte, 256), RequestBufferLim: 128})
|
|
||||||
value := []byte("text/html")
|
|
||||||
if !exch.StageHeaderBytes("Content-Type", value) {
|
|
||||||
t.Fatal("want field staged")
|
|
||||||
}
|
|
||||||
// The value must be copied, not aliased: mutating it after staging must not
|
|
||||||
// change what reaches the wire.
|
|
||||||
value[0] = 'X'
|
|
||||||
exch.WriteHeader(200)
|
|
||||||
const want = "HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n\r\n"
|
|
||||||
if got := conn.ViewWritten(); got != want {
|
|
||||||
t.Errorf("want %q, got %q", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Respond replaces the stage/stage/stage/write boilerplate every handler paid,
|
// Respond replaces the stage/stage/stage/write boilerplate every handler paid,
|
||||||
// deriving Content-Length from the body so it cannot disagree with what is sent.
|
// deriving Content-Length from the body so it cannot disagree with what is sent.
|
||||||
func TestExchangeRespond(t *testing.T) {
|
func TestExchangeRespond(t *testing.T) {
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ func TestFormParseReuseNoAlloc(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
allocs := testing.AllocsPerRun(100, func() {
|
allocs := testing.AllocsPerRun(100, func() {
|
||||||
f.Reset(body, 0)
|
f.Reset(body, 0) // 0 preserves the pair storage warmed up above, the reuse under test.
|
||||||
f.Parse()
|
f.Parse()
|
||||||
})
|
})
|
||||||
if allocs != 0 {
|
if allocs != 0 {
|
||||||
@@ -161,7 +161,7 @@ func TestFormParseReuseNoAlloc(t *testing.T) {
|
|||||||
// one. Form.Len is zero until Parse runs and cannot answer that.
|
// one. Form.Len is zero until Parse runs and cannot answer that.
|
||||||
func TestFormBufferUsed(t *testing.T) {
|
func TestFormBufferUsed(t *testing.T) {
|
||||||
var f Form
|
var f Form
|
||||||
f.Reset(nil, 0)
|
f.Reset(nil, defaultKVCap)
|
||||||
if got := f.BufferUsed(); got != 0 {
|
if got := f.BufferUsed(); got != 0 {
|
||||||
t.Errorf("want 0 on a fresh form, got %d", got)
|
t.Errorf("want 0 on a fresh form, got %d", got)
|
||||||
}
|
}
|
||||||
@@ -191,7 +191,7 @@ func TestFormBufferUsed(t *testing.T) {
|
|||||||
t.Errorf("BufferUsed must not change on Parse, got %d", got)
|
t.Errorf("BufferUsed must not change on Parse, got %d", got)
|
||||||
}
|
}
|
||||||
// Reset discards the pairs and the buffered bytes with them.
|
// Reset discards the pairs and the buffered bytes with them.
|
||||||
f.Reset(nil, 0)
|
f.Reset(nil, defaultKVCap)
|
||||||
if got := f.BufferUsed(); got != 0 {
|
if got := f.BufferUsed(); got != 0 {
|
||||||
t.Errorf("want 0 after Reset, got %d", got)
|
t.Errorf("want 0 after Reset, got %d", got)
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-12
@@ -11,7 +11,11 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const numHeaderCapacity = 16
|
// defaultKVCap is the key/value table size tests hand to Reset. A Reset with 0
|
||||||
|
// preserves whatever capacity the value already had, which is what production
|
||||||
|
// code wants on reuse but leaves a fresh value unable to hold a single pair when
|
||||||
|
// growth is disabled, see [Form.Reset].
|
||||||
|
const defaultKVCap = 16
|
||||||
|
|
||||||
func TestHeaderParseRequest(t *testing.T) {
|
func TestHeaderParseRequest(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
@@ -62,7 +66,7 @@ func TestHeaderParseRequest(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var c Cookie
|
var c Cookie
|
||||||
cookie := hdr.Get("Cookie")
|
cookie := hdr.Get("Cookie")
|
||||||
c.Reset(cookie, 0)
|
c.Reset(cookie, defaultKVCap)
|
||||||
err = c.Parse()
|
err = c.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -394,7 +398,7 @@ func TestCopyDecodedPercentURLInPlace(t *testing.T) {
|
|||||||
|
|
||||||
func TestHeaderSetOverwrite(t *testing.T) {
|
func TestHeaderSetOverwrite(t *testing.T) {
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(nil, numHeaderCapacity)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetMethod("GET")
|
h.SetMethod("GET")
|
||||||
h.SetRequestTarget("/")
|
h.SetRequestTarget("/")
|
||||||
h.SetProtocol("HTTP/1.1")
|
h.SetProtocol("HTTP/1.1")
|
||||||
@@ -417,7 +421,7 @@ func TestHeaderSetOverwrite(t *testing.T) {
|
|||||||
|
|
||||||
func TestHeaderSetBytesEmptyValue(t *testing.T) {
|
func TestHeaderSetBytesEmptyValue(t *testing.T) {
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(nil, numHeaderCapacity)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetBytes("X-Empty", nil)
|
h.SetBytes("X-Empty", nil)
|
||||||
if got := h.Get("X-Empty"); len(got) != 0 {
|
if got := h.Get("X-Empty"); len(got) != 0 {
|
||||||
t.Errorf("want empty value, got %q", got)
|
t.Errorf("want empty value, got %q", got)
|
||||||
@@ -472,7 +476,7 @@ func TestHeader_SplitBeforeColonStillParses(t *testing.T) {
|
|||||||
const part2 = ": example.com\r\n\r\n"
|
const part2 = ": example.com\r\n\r\n"
|
||||||
|
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(nil, numHeaderCapacity)
|
h.Reset(nil, defaultKVCap)
|
||||||
if err := h.ReadFromBytes([]byte(part1)); err != nil {
|
if err := h.ReadFromBytes([]byte(part1)); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -505,7 +509,7 @@ func TestHeader_AppendHeaderExactCapNoPanic(t *testing.T) {
|
|||||||
const key, value = "K", "V"
|
const key, value = "K", "V"
|
||||||
buf := make([]byte, 0, len(key)+len(value)) // exact cap, no slack.
|
buf := make([]byte, 0, len(key)+len(value)) // exact cap, no slack.
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(buf, numHeaderCapacity)
|
h.Reset(buf, defaultKVCap)
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
t.Fatalf("appendHeader panicked on exact-cap buffer: %v", r)
|
t.Fatalf("appendHeader panicked on exact-cap buffer: %v", r)
|
||||||
@@ -522,7 +526,7 @@ func TestHeader_AppendHeaderExactCapNoPanic(t *testing.T) {
|
|||||||
func TestHeader_AddFullBufferNoPanic(t *testing.T) {
|
func TestHeader_AddFullBufferNoPanic(t *testing.T) {
|
||||||
buf := make([]byte, 0, 40) // Small cap; enough for Reset (len 0) but not the field below.
|
buf := make([]byte, 0, 40) // Small cap; enough for Reset (len 0) but not the field below.
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(buf, numHeaderCapacity)
|
h.Reset(buf, defaultKVCap)
|
||||||
h.ConfigBufferGrowth(false)
|
h.ConfigBufferGrowth(false)
|
||||||
h.SetMethod("GET")
|
h.SetMethod("GET")
|
||||||
h.SetRequestTarget("/")
|
h.SetRequestTarget("/")
|
||||||
@@ -557,7 +561,7 @@ func TestHeader_SetInt(t *testing.T) {
|
|||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(nil, numHeaderCapacity)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetInt("Content-Length", tc.value, tc.base)
|
h.SetInt("Content-Length", tc.value, tc.base)
|
||||||
if got := string(h.Get("Content-Length")); got != tc.want {
|
if got := string(h.Get("Content-Length")); got != tc.want {
|
||||||
t.Fatalf("want %q, got %q", tc.want, got)
|
t.Fatalf("want %q, got %q", tc.want, got)
|
||||||
@@ -569,7 +573,7 @@ func TestHeader_SetInt(t *testing.T) {
|
|||||||
// SetInt on an existing key must reuse the slot in place (single field, latest value).
|
// SetInt on an existing key must reuse the slot in place (single field, latest value).
|
||||||
func TestHeader_SetIntOverwrite(t *testing.T) {
|
func TestHeader_SetIntOverwrite(t *testing.T) {
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(nil, numHeaderCapacity)
|
h.Reset(nil, defaultKVCap)
|
||||||
h.SetMethod("GET")
|
h.SetMethod("GET")
|
||||||
h.SetRequestTarget("/")
|
h.SetRequestTarget("/")
|
||||||
h.SetProtocol("HTTP/1.1")
|
h.SetProtocol("HTTP/1.1")
|
||||||
@@ -593,7 +597,7 @@ func TestHeader_SetIntOverwrite(t *testing.T) {
|
|||||||
func TestHeader_SetIntNoAlloc(t *testing.T) {
|
func TestHeader_SetIntNoAlloc(t *testing.T) {
|
||||||
buf := make([]byte, 0, 256)
|
buf := make([]byte, 0, 256)
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(buf, numHeaderCapacity)
|
h.Reset(buf, defaultKVCap)
|
||||||
h.ConfigBufferGrowth(false)
|
h.ConfigBufferGrowth(false)
|
||||||
h.Add("Content-Length", "0000000000000000000000") // pre-size a reusable slot.
|
h.Add("Content-Length", "0000000000000000000000") // pre-size a reusable slot.
|
||||||
allocs := testing.AllocsPerRun(100, func() {
|
allocs := testing.AllocsPerRun(100, func() {
|
||||||
@@ -623,7 +627,7 @@ func TestHeader_FieldTableSizedFromBuffer(t *testing.T) {
|
|||||||
raw.WriteString("X-Canary: " + wantVal + "\r\n\r\n")
|
raw.WriteString("X-Canary: " + wantVal + "\r\n\r\n")
|
||||||
|
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(make([]byte, 0, 8192), numHeaderCapacity) // Room for the block with plenty to spare.
|
h.Reset(make([]byte, 0, 8192), defaultKVCap) // Room for the block with plenty to spare.
|
||||||
err := h.ParseBytes(false, []byte(raw.String()))
|
err := h.ParseBytes(false, []byte(raw.String()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("parsing a 42 field request into an 8kB buffer: %s", err)
|
t.Fatalf("parsing a 42 field request into an 8kB buffer: %s", err)
|
||||||
@@ -646,7 +650,7 @@ func TestHeader_FieldTableFullIsReported(t *testing.T) {
|
|||||||
}
|
}
|
||||||
raw.WriteString("\r\n")
|
raw.WriteString("\r\n")
|
||||||
var h Header
|
var h Header
|
||||||
h.Reset(make([]byte, 0, 512), numHeaderCapacity)
|
h.Reset(make([]byte, 0, 512), defaultKVCap)
|
||||||
h.ConfigBufferGrowth(false)
|
h.ConfigBufferGrowth(false)
|
||||||
err := h.ParseBytes(false, []byte(raw.String()))
|
err := h.ParseBytes(false, []byte(raw.String()))
|
||||||
if !errors.Is(err, ErrHeaderTooMany) {
|
if !errors.Is(err, ErrHeaderTooMany) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ func TestTryParse_IncrementalRequest(t *testing.T) {
|
|||||||
// Full HTTP request split across multiple ReadFromBytes calls.
|
// Full HTTP request split across multiple ReadFromBytes calls.
|
||||||
full := "GET /index.html HTTP/1.1\r\nHost: example.com\r\nContent-Type: text/html\r\n\r\nbody here"
|
full := "GET /index.html HTTP/1.1\r\nHost: example.com\r\nContent-Type: text/html\r\n\r\nbody here"
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
// Feed data in small chunks to exercise incremental parsing.
|
// Feed data in small chunks to exercise incremental parsing.
|
||||||
chunks := splitInto(full, 10)
|
chunks := splitInto(full, 10)
|
||||||
@@ -79,7 +79,7 @@ func TestTryParse_IncrementalRequest(t *testing.T) {
|
|||||||
func TestTryParse_IncrementalResponse(t *testing.T) {
|
func TestTryParse_IncrementalResponse(t *testing.T) {
|
||||||
full := "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nServer: lneto\r\n\r\nhello"
|
full := "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nServer: lneto\r\n\r\nhello"
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
chunks := splitInto(full, 8)
|
chunks := splitInto(full, 8)
|
||||||
var done bool
|
var done bool
|
||||||
@@ -131,7 +131,7 @@ func TestReadFromLimited(t *testing.T) {
|
|||||||
r := strings.NewReader(data)
|
r := strings.NewReader(data)
|
||||||
|
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
// Read in one shot.
|
// Read in one shot.
|
||||||
n, err := hdr.ReadFromLimited(r, 256)
|
n, err := hdr.ReadFromLimited(r, 256)
|
||||||
@@ -157,7 +157,7 @@ func TestReadFromLimited(t *testing.T) {
|
|||||||
|
|
||||||
func TestReadFromLimited_MaxBytes(t *testing.T) {
|
func TestReadFromLimited_MaxBytes(t *testing.T) {
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
// Zero maxBytesToRead should error.
|
// Zero maxBytesToRead should error.
|
||||||
_, err := hdr.ReadFromLimited(strings.NewReader("data"), 0)
|
_, err := hdr.ReadFromLimited(strings.NewReader("data"), 0)
|
||||||
@@ -168,7 +168,7 @@ func TestReadFromLimited_MaxBytes(t *testing.T) {
|
|||||||
|
|
||||||
func TestReadFromBytes_Empty(t *testing.T) {
|
func TestReadFromBytes_Empty(t *testing.T) {
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
|
|
||||||
err := hdr.ReadFromBytes(nil)
|
err := hdr.ReadFromBytes(nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -178,7 +178,7 @@ func TestReadFromBytes_Empty(t *testing.T) {
|
|||||||
|
|
||||||
func TestBufferFreeAndCapacity(t *testing.T) {
|
func TestBufferFreeAndCapacity(t *testing.T) {
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 100), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 100), defaultKVCap)
|
||||||
|
|
||||||
if hdr.BufferCapacity() != 100 {
|
if hdr.BufferCapacity() != 100 {
|
||||||
t.Errorf("capacity = %d; want 100", hdr.BufferCapacity())
|
t.Errorf("capacity = %d; want 100", hdr.BufferCapacity())
|
||||||
@@ -196,7 +196,7 @@ func TestBufferFreeAndCapacity(t *testing.T) {
|
|||||||
func TestEnableBufferGrowth(t *testing.T) {
|
func TestEnableBufferGrowth(t *testing.T) {
|
||||||
var hdr Header
|
var hdr Header
|
||||||
buf := make([]byte, 0, 64)
|
buf := make([]byte, 0, 64)
|
||||||
hdr.Reset(buf, numHeaderCapacity)
|
hdr.Reset(buf, defaultKVCap)
|
||||||
hdr.ConfigBufferGrowth(false)
|
hdr.ConfigBufferGrowth(false)
|
||||||
// With growth disabled, reading more than capacity should fail.
|
// With growth disabled, reading more than capacity should fail.
|
||||||
big := make([]byte, 128)
|
big := make([]byte, 128)
|
||||||
@@ -387,7 +387,7 @@ func TestHeader_MultilineValue(t *testing.T) {
|
|||||||
|
|
||||||
func TestHeader_ResponseRoundTrip(t *testing.T) {
|
func TestHeader_ResponseRoundTrip(t *testing.T) {
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
hdr.SetProtocol("HTTP/1.1")
|
hdr.SetProtocol("HTTP/1.1")
|
||||||
hdr.SetStatus("404", "Not Found")
|
hdr.SetStatus("404", "Not Found")
|
||||||
hdr.Add("Content-Type", "text/plain")
|
hdr.Add("Content-Type", "text/plain")
|
||||||
@@ -427,7 +427,7 @@ func TestHeader_ResponseRoundTrip(t *testing.T) {
|
|||||||
|
|
||||||
func TestHeader_RequestRoundTrip(t *testing.T) {
|
func TestHeader_RequestRoundTrip(t *testing.T) {
|
||||||
var hdr Header
|
var hdr Header
|
||||||
hdr.Reset(make([]byte, 0, 256), numHeaderCapacity)
|
hdr.Reset(make([]byte, 0, 256), defaultKVCap)
|
||||||
hdr.SetProtocol("HTTP/1.1")
|
hdr.SetProtocol("HTTP/1.1")
|
||||||
hdr.SetMethod("POST")
|
hdr.SetMethod("POST")
|
||||||
hdr.SetRequestTarget("/api/data")
|
hdr.SetRequestTarget("/api/data")
|
||||||
|
|||||||
Reference in New Issue
Block a user