Compare commits
	
		
			7 Commits
		
	
	
		
			a551e911ab
			...
			92f2f6895b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 92f2f6895b | |||
| c1d2e38840 | |||
| f89f21a47f | |||
| fa85ea8208 | |||
| b728235b6c | |||
| d31ab67268 | |||
| b2b3093e0e | 
| @ -206,13 +206,14 @@ function codegen_enum(name, cg,    gotype, fields) { | |||||||
| 	print "}" | 	print "}" | ||||||
| 	print "" | 	print "" | ||||||
| 
 | 
 | ||||||
|  | 	CodegenIsMarshaler[name] = 1 | ||||||
| 	fields = cg["marshal"] | 	fields = cg["marshal"] | ||||||
| 	sub(/,\n$/, ":", fields) | 	sub(/,\n$/, ":", fields) | ||||||
| 	gsub(/\n/, "\n\t", fields) | 	gsub(/\n/, "\n\t", fields) | ||||||
| 	print "func (v " gotype ") MarshalJSON() ([]byte, error) {" | 	print "func (v " gotype ") MarshalJSON() ([]byte, error) {" | ||||||
| 	print "\tswitch v {" | 	print "\tswitch v {" | ||||||
| 	print indent("case " fields) | 	print indent("case " fields) | ||||||
| 	print "\t\treturn json.Marshal(v.String())" | 	print "\t\treturn []byte(`\"` + v.String() + `\"`), nil" | ||||||
| 	print "\t}" | 	print "\t}" | ||||||
| 	print "\treturn json.Marshal(int(v))" | 	print "\treturn json.Marshal(int(v))" | ||||||
| 	print "}" | 	print "}" | ||||||
| @ -252,7 +253,50 @@ function codegen_enum(name, cg,    gotype, fields) { | |||||||
| 		delete cg[i] | 		delete cg[i] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function codegen_struct_field_marshal(d, cg,    camel, f, marshal) { | ||||||
|  | 	camel = snaketocamel(d["name"]) | ||||||
|  | 	f = "s." camel | ||||||
|  | 
 | ||||||
|  | 	# Complex types are json.Marshalers, there's no need to json.Marshal(&f). | ||||||
|  | 	if (!d["isarray"]) { | ||||||
|  | 		if (CodegenIsMarshaler[d["type"]]) | ||||||
|  | 			marshal = f ".MarshalJSON()" | ||||||
|  | 		else | ||||||
|  | 			marshal = "json.Marshal(" f ")" | ||||||
|  | 
 | ||||||
|  | 		append(cg, "marshal", | ||||||
|  | 			"\tb = append(b, `,\"" decapitalize(camel) "\":`...)\n" \ | ||||||
|  | 			"\tif j, err := " marshal "; err != nil {\n" \ | ||||||
|  | 			"\t\treturn nil, err\n" \ | ||||||
|  | 			"\t} else {\n" \ | ||||||
|  | 			"\t\tb = append(b, j...)\n" \ | ||||||
|  | 			"\t}\n") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (CodegenIsMarshaler[d["type"]]) | ||||||
|  | 		marshal = f "[i].MarshalJSON()" | ||||||
|  | 	else | ||||||
|  | 		marshal = "json.Marshal(" f "[i])" | ||||||
|  | 
 | ||||||
|  | 	append(cg, "marshal", | ||||||
|  | 		"\tb = append(b, `,\"" decapitalize(camel) "\":[`...)\n" \ | ||||||
|  | 		"\tfor i := 0; i < len(" f "); i++ {\n" \ | ||||||
|  | 		"\t\tif i > 0 {\n" \ | ||||||
|  | 		"\t\t\tb = append(b, ',')\n" \ | ||||||
|  | 		"\t\t}\n" \ | ||||||
|  | 		"\t\tif j, err := " marshal "; err != nil {\n" \ | ||||||
|  | 		"\t\t\treturn nil, err\n" \ | ||||||
|  | 		"\t\t} else {\n" \ | ||||||
|  | 		"\t\t\tb = append(b, j...)\n" \ | ||||||
|  | 		"\t\t}\n" \ | ||||||
|  | 		"\t}\n" \ | ||||||
|  | 		"\tb = append(b, ']')\n") | ||||||
|  | } | ||||||
|  | 
 | ||||||
| function codegen_struct_field(d, cg,    camel, f, serialize, deserialize) { | function codegen_struct_field(d, cg,    camel, f, serialize, deserialize) { | ||||||
|  | 	codegen_struct_field_marshal(d, cg) | ||||||
|  | 
 | ||||||
| 	camel = snaketocamel(d["name"]) | 	camel = snaketocamel(d["name"]) | ||||||
| 	f = "s." camel | 	f = "s." camel | ||||||
| 	serialize = CodegenSerialize[d["type"]] | 	serialize = CodegenSerialize[d["type"]] | ||||||
| @ -303,6 +347,8 @@ function codegen_struct_field(d, cg,    camel, f, serialize, deserialize) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function codegen_struct_tag(d, cg,    camel, f) { | function codegen_struct_tag(d, cg,    camel, f) { | ||||||
|  | 	codegen_struct_field_marshal(d, cg) | ||||||
|  | 
 | ||||||
| 	camel = snaketocamel(d["name"]) | 	camel = snaketocamel(d["name"]) | ||||||
| 	f = "s." camel | 	f = "s." camel | ||||||
| 	append(cg, "fields", "\t" camel " " CodegenGoType[d["type"]] \ | 	append(cg, "fields", "\t" camel " " CodegenGoType[d["type"]] \ | ||||||
| @ -315,6 +361,16 @@ function codegen_struct(name, cg,    gotype) { | |||||||
| 	gotype = PrefixCamel name | 	gotype = PrefixCamel name | ||||||
| 	print "type " gotype " struct {\n" cg["fields"] "}\n" | 	print "type " gotype " struct {\n" cg["fields"] "}\n" | ||||||
| 
 | 
 | ||||||
|  | 	if (cg["marshal"]) { | ||||||
|  | 		CodegenIsMarshaler[name] = 1 | ||||||
|  | 		print "func (s *" gotype ") MarshalJSON() ([]byte, error) {" | ||||||
|  | 		print "\tb := []byte{}" | ||||||
|  | 		print cg["marshal"] "\tb[0] = '{'" | ||||||
|  | 		print "\treturn append(b, '}'), nil" | ||||||
|  | 		print "}" | ||||||
|  | 		print "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	if (cg["serialize"]) { | 	if (cg["serialize"]) { | ||||||
| 		print "func (s *" gotype ") AppendTo(data []byte) ([]byte, bool) {" | 		print "func (s *" gotype ") AppendTo(data []byte) ([]byte, bool) {" | ||||||
| 		print "\tok := true" | 		print "\tok := true" | ||||||
| @ -362,15 +418,15 @@ function codegen_union_struct(name, casename, cg, scg,     structname, init) { | |||||||
| 		"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \ | 		"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \ | ||||||
| 		"\t\ts := " init "\n" \ | 		"\t\ts := " init "\n" \ | ||||||
| 		"\t\terr = json.Unmarshal(data, &s)\n" \ | 		"\t\terr = json.Unmarshal(data, &s)\n" \ | ||||||
| 		"\t\tu.Interface = s\n") | 		"\t\tu.Interface = &s\n") | ||||||
| 	append(cg, "serialize", | 	append(cg, "serialize", | ||||||
| 		"\tcase " CodegenGoType[structname] ":\n" \ | 		"\tcase *" CodegenGoType[structname] ":\n" \ | ||||||
| 		indent(sprintf(CodegenSerialize[structname], "union"))) | 		indent(sprintf(CodegenSerialize[structname], "union"))) | ||||||
| 	append(cg, "deserialize", | 	append(cg, "deserialize", | ||||||
| 		"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \ | 		"\tcase " CodegenGoType[cg["tagtype"]] snaketocamel(casename) ":\n" \ | ||||||
| 		"\t\ts := " init "\n" \ | 		"\t\ts := " init "\n" \ | ||||||
| 		indent(sprintf(CodegenDeserialize[structname], "s")) \ | 		indent(sprintf(CodegenDeserialize[structname], "s")) \ | ||||||
| 		"\t\tu.Interface = s\n") | 		"\t\tu.Interface = &s\n") | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function codegen_union(name, cg,    gotype, tagfield, tagvar) { | function codegen_union(name, cg,    gotype, tagfield, tagvar) { | ||||||
| @ -381,8 +437,9 @@ function codegen_union(name, cg,    gotype, tagfield, tagvar) { | |||||||
| 	print "" | 	print "" | ||||||
| 
 | 
 | ||||||
| 	# This cannot be a pointer method, it wouldn't work recursively. | 	# This cannot be a pointer method, it wouldn't work recursively. | ||||||
|  | 	CodegenIsMarshaler[name] = 1 | ||||||
| 	print "func (u " gotype ") MarshalJSON() ([]byte, error) {" | 	print "func (u " gotype ") MarshalJSON() ([]byte, error) {" | ||||||
| 	print "\treturn json.Marshal(u.Interface)" | 	print "\treturn u.Interface.(json.Marshaler).MarshalJSON()" | ||||||
| 	print "}" | 	print "}" | ||||||
| 	print "" | 	print "" | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								xC.c
									
									
									
									
									
								
							| @ -1541,6 +1541,7 @@ struct formatter | |||||||
| 	struct app_context *ctx;            ///< Application context
 | 	struct app_context *ctx;            ///< Application context
 | ||||||
| 	struct server *s;                   ///< Server
 | 	struct server *s;                   ///< Server
 | ||||||
| 
 | 
 | ||||||
|  | 	bool clean;                         ///< Assume ATTR_RESET
 | ||||||
| 	struct formatter_item *items;       ///< Items
 | 	struct formatter_item *items;       ///< Items
 | ||||||
| 	size_t items_len;                   ///< Items used
 | 	size_t items_len;                   ///< Items used
 | ||||||
| 	size_t items_alloc;                 ///< Items allocated
 | 	size_t items_alloc;                 ///< Items allocated
 | ||||||
| @ -1549,7 +1550,7 @@ struct formatter | |||||||
| static struct formatter | static struct formatter | ||||||
| formatter_make (struct app_context *ctx, struct server *s) | formatter_make (struct app_context *ctx, struct server *s) | ||||||
| { | { | ||||||
| 	struct formatter self = { .ctx = ctx, .s = s }; | 	struct formatter self = { .ctx = ctx, .s = s, .clean = true }; | ||||||
| 	self.items = xcalloc (sizeof *self.items, (self.items_alloc = 16)); | 	self.items = xcalloc (sizeof *self.items, (self.items_alloc = 16)); | ||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
| @ -3811,7 +3812,7 @@ irc_to_utf8 (const char *text) | |||||||
| //   #d inserts a signed integer
 | //   #d inserts a signed integer
 | ||||||
| //   #l inserts a locale-encoded string
 | //   #l inserts a locale-encoded string
 | ||||||
| //
 | //
 | ||||||
| //   #S inserts a string from the server with unknown encoding
 | //   #S inserts a string from the server in an unknown encoding
 | ||||||
| //   #m inserts an IRC-formatted string (auto-resets at boundaries)
 | //   #m inserts an IRC-formatted string (auto-resets at boundaries)
 | ||||||
| //   #n cuts the nickname from a string and automatically colours it
 | //   #n cuts the nickname from a string and automatically colours it
 | ||||||
| //   #N is like #n but also appends userhost, if present
 | //   #N is like #n but also appends userhost, if present
 | ||||||
| @ -3827,6 +3828,17 @@ irc_to_utf8 (const char *text) | |||||||
| static void | static void | ||||||
| formatter_add_item (struct formatter *self, struct formatter_item template_) | formatter_add_item (struct formatter *self, struct formatter_item template_) | ||||||
| { | { | ||||||
|  | 	// Auto-resetting tends to create unnecessary items,
 | ||||||
|  | 	// which also end up being relayed to frontends, so filter them out.
 | ||||||
|  | 	bool reset = | ||||||
|  | 		template_.type == FORMATTER_ITEM_ATTR && | ||||||
|  | 		template_.attribute == ATTR_RESET; | ||||||
|  | 	if (self->clean && reset) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	self->clean = reset || | ||||||
|  | 		(self->clean && template_.type == FORMATTER_ITEM_TEXT); | ||||||
|  | 
 | ||||||
| 	if (template_.text) | 	if (template_.text) | ||||||
| 		template_.text = xstrdup (template_.text); | 		template_.text = xstrdup (template_.text); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,4 +2,9 @@ module janouch.name/xK/xP | |||||||
| 
 | 
 | ||||||
| go 1.18 | go 1.18 | ||||||
| 
 | 
 | ||||||
| require golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d | require nhooyr.io/websocket v1.8.7 | ||||||
|  | 
 | ||||||
|  | require ( | ||||||
|  | 	github.com/klauspost/compress v1.15.9 // indirect | ||||||
|  | 	golang.org/x/sys v0.0.0-20210423082822-04245dca01da // indirect | ||||||
|  | ) | ||||||
|  | |||||||
							
								
								
									
										64
									
								
								xP/go.sum
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								xP/go.sum
									
									
									
									
									
								
							| @ -1,2 +1,62 @@ | |||||||
| golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
|  | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= | ||||||
|  | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= | ||||||
|  | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= | ||||||
|  | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= | ||||||
|  | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= | ||||||
|  | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= | ||||||
|  | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= | ||||||
|  | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= | ||||||
|  | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= | ||||||
|  | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= | ||||||
|  | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= | ||||||
|  | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= | ||||||
|  | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= | ||||||
|  | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= | ||||||
|  | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= | ||||||
|  | github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= | ||||||
|  | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= | ||||||
|  | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= | ||||||
|  | github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= | ||||||
|  | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= | ||||||
|  | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= | ||||||
|  | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= | ||||||
|  | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= | ||||||
|  | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= | ||||||
|  | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | ||||||
|  | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= | ||||||
|  | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= | ||||||
|  | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= | ||||||
|  | github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= | ||||||
|  | github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= | ||||||
|  | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= | ||||||
|  | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= | ||||||
|  | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= | ||||||
|  | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= | ||||||
|  | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | ||||||
|  | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= | ||||||
|  | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | ||||||
|  | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
|  | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
|  | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | ||||||
|  | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||||||
|  | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= | ||||||
|  | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= | ||||||
|  | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= | ||||||
|  | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= | ||||||
|  | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= | ||||||
|  | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
|  | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | ||||||
|  | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||||
|  | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= | ||||||
|  | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||||
|  | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
|  | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= | ||||||
|  | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
|  | nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= | ||||||
|  | nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= | ||||||
|  | |||||||
| @ -56,8 +56,6 @@ class RelayRpc extends EventTarget { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	_process(data) { | 	_process(data) { | ||||||
| 		console.log(data) |  | ||||||
| 
 |  | ||||||
| 		if (typeof data !== 'string') | 		if (typeof data !== 'string') | ||||||
| 			throw "Binary messages not supported" | 			throw "Binary messages not supported" | ||||||
| 
 | 
 | ||||||
| @ -85,11 +83,8 @@ class RelayRpc extends EventTarget { | |||||||
| 			if (typeof e.event !== 'string') | 			if (typeof e.event !== 'string') | ||||||
| 				throw "Invalid event tag" | 				throw "Invalid event tag" | ||||||
| 
 | 
 | ||||||
| 			this.dispatchEvent(new CustomEvent( | 			e.eventSeq = message.eventSeq | ||||||
| 				e.event, {detail: {eventSeq: message.eventSeq, ...e}})) | 			this.dispatchEvent(new CustomEvent('event', {detail: e})) | ||||||
| 
 |  | ||||||
| 			// Minor abstraction layering violation.
 |  | ||||||
| 			m.redraw() |  | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -186,6 +181,7 @@ function updateIcon(highlighted) { | |||||||
| // ---- Event processing -------------------------------------------------------
 | // ---- Event processing -------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| let rpc = new RelayRpc(proxy) | let rpc = new RelayRpc(proxy) | ||||||
|  | let rpcEventHandlers = {} | ||||||
| 
 | 
 | ||||||
| let buffers = new Map() | let buffers = new Map() | ||||||
| let bufferLast = undefined | let bufferLast = undefined | ||||||
| @ -250,14 +246,23 @@ rpc.addEventListener('close', event => { | |||||||
| 	m.redraw() | 	m.redraw() | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('Ping', event => { | rpc.addEventListener('event', event => { | ||||||
| 	rpc.send({command: 'PingResponse', eventSeq: event.detail.eventSeq}) | 	const handler = rpcEventHandlers[event.detail.event] | ||||||
|  | 	if (handler !== undefined) { | ||||||
|  | 		handler(event.detail) | ||||||
|  | 		if (bufferCurrent !== undefined || event.detail.event !== 'BufferLine') | ||||||
|  | 			m.redraw() | ||||||
|  | 	} | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
|  | rpcEventHandlers['Ping'] = e => { | ||||||
|  | 	rpc.send({command: 'PingResponse', eventSeq: e.eventSeq}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // ~~~ Buffer events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | // ~~~ Buffer events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferUpdate', event => { | rpcEventHandlers['BufferUpdate'] = e => { | ||||||
| 	let e = event.detail, b = buffers.get(e.bufferName) | 	let b = buffers.get(e.bufferName) | ||||||
| 	if (b === undefined) { | 	if (b === undefined) { | ||||||
| 		buffers.set(e.bufferName, (b = { | 		buffers.set(e.bufferName, (b = { | ||||||
| 			lines: [], | 			lines: [], | ||||||
| @ -270,38 +275,36 @@ rpc.addEventListener('BufferUpdate', event => { | |||||||
| 	b.hideUnimportant = e.hideUnimportant | 	b.hideUnimportant = e.hideUnimportant | ||||||
| 	b.kind = e.context.kind | 	b.kind = e.context.kind | ||||||
| 	b.server = servers.get(e.context.serverName) | 	b.server = servers.get(e.context.serverName) | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferStats', event => { | rpcEventHandlers['BufferStats'] = e => { | ||||||
| 	let e = event.detail, b = buffers.get(e.bufferName) | 	let b = buffers.get(e.bufferName) | ||||||
| 	if (b === undefined) | 	if (b === undefined) | ||||||
| 		return | 		return | ||||||
| 
 | 
 | ||||||
| 	b.newMessages = e.newMessages, | 	b.newMessages = e.newMessages, | ||||||
| 	b.newUnimportantMessages = e.newUnimportantMessages | 	b.newUnimportantMessages = e.newUnimportantMessages | ||||||
| 	b.highlighted = e.highlighted | 	b.highlighted = e.highlighted | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferRename', event => { | rpcEventHandlers['BufferRename'] = e => { | ||||||
| 	let e = event.detail |  | ||||||
| 	buffers.set(e.new, buffers.get(e.bufferName)) | 	buffers.set(e.new, buffers.get(e.bufferName)) | ||||||
| 	buffers.delete(e.bufferName) | 	buffers.delete(e.bufferName) | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferRemove', event => { | rpcEventHandlers['BufferRemove'] = e => { | ||||||
| 	let e = event.detail |  | ||||||
| 	buffers.delete(e.bufferName) | 	buffers.delete(e.bufferName) | ||||||
| 	if (e.bufferName === bufferLast) | 	if (e.bufferName === bufferLast) | ||||||
| 		bufferLast = undefined | 		bufferLast = undefined | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferActivate', event => { | rpcEventHandlers['BufferActivate'] = e => { | ||||||
| 	let old = buffers.get(bufferCurrent) | 	let old = buffers.get(bufferCurrent) | ||||||
| 	if (old !== undefined) | 	if (old !== undefined) | ||||||
| 		bufferResetStats(old) | 		bufferResetStats(old) | ||||||
| 
 | 
 | ||||||
| 	bufferLast = bufferCurrent | 	bufferLast = bufferCurrent | ||||||
| 	let e = event.detail, b = buffers.get(e.bufferName) | 	let b = buffers.get(e.bufferName) | ||||||
| 	bufferCurrent = e.bufferName | 	bufferCurrent = e.bufferName | ||||||
| 	bufferLog = undefined | 	bufferLog = undefined | ||||||
| 	bufferAutoscroll = true | 	bufferAutoscroll = true | ||||||
| @ -328,11 +331,12 @@ rpc.addEventListener('BufferActivate', event => { | |||||||
| 		textarea.value = b.input | 		textarea.value = b.input | ||||||
| 		textarea.setSelectionRange(b.inputStart, b.inputEnd, b.inputDirection) | 		textarea.setSelectionRange(b.inputStart, b.inputEnd, b.inputDirection) | ||||||
| 	} | 	} | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferLine', event => { | rpcEventHandlers['BufferLine'] = e => { | ||||||
| 	let e = event.detail, b = buffers.get(e.bufferName), line = {...e} | 	let b = buffers.get(e.bufferName), line = {...e} | ||||||
| 	delete line.event | 	delete line.event | ||||||
|  | 	delete line.eventSeq | ||||||
| 	delete line.leakToActive | 	delete line.leakToActive | ||||||
| 	if (b === undefined) | 	if (b === undefined) | ||||||
| 		return | 		return | ||||||
| @ -372,33 +376,31 @@ rpc.addEventListener('BufferLine', event => { | |||||||
| 		if (!visible) | 		if (!visible) | ||||||
| 			b.highlighted = true | 			b.highlighted = true | ||||||
| 	} | 	} | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferClear', event => { | rpcEventHandlers['BufferClear'] = e => { | ||||||
| 	let e = event.detail, b = buffers.get(e.bufferName) | 	let b = buffers.get(e.bufferName) | ||||||
| 	if (b !== undefined) | 	if (b !== undefined) | ||||||
| 		b.lines.length = 0 | 		b.lines.length = 0 | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| // ~~~ Server events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | // ~~~ Server events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('ServerUpdate', event => { | rpcEventHandlers['ServerUpdate'] = e => { | ||||||
| 	let e = event.detail, s = servers.get(e.serverName) | 	let s = servers.get(e.serverName) | ||||||
| 	if (s === undefined) | 	if (s === undefined) | ||||||
| 		servers.set(e.serverName, (s = {})) | 		servers.set(e.serverName, (s = {})) | ||||||
| 	s.state = e.state | 	s.state = e.state | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('ServerRename', event => { | rpcEventHandlers['ServerRename'] = e => { | ||||||
| 	let e = event.detail |  | ||||||
| 	servers.set(e.new, servers.get(e.serverName)) | 	servers.set(e.new, servers.get(e.serverName)) | ||||||
| 	servers.delete(e.serverName) | 	servers.delete(e.serverName) | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('ServerRemove', event => { | rpcEventHandlers['ServerRemove'] = e => { | ||||||
| 	let e = event.detail |  | ||||||
| 	servers.delete(e.serverName) | 	servers.delete(e.serverName) | ||||||
| }) | } | ||||||
| 
 | 
 | ||||||
| // --- Colours -----------------------------------------------------------------
 | // --- Colours -----------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										168
									
								
								xP/xP.go
									
									
									
									
									
								
							
							
						
						
									
										168
									
								
								xP/xP.go
									
									
									
									
									
								
							| @ -4,6 +4,7 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"bufio" | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| @ -16,7 +17,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"golang.org/x/net/websocket" | 	"nhooyr.io/websocket" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| @ -25,18 +26,62 @@ var ( | |||||||
| 	addressWS      string | 	addressWS      string | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func clientToRelay( | // ----------------------------------------------------------------------------- | ||||||
| 	ctx context.Context, ws *websocket.Conn, conn net.Conn) bool { | 
 | ||||||
| 	var j string | func relayReadJSON(r io.Reader) []byte { | ||||||
| 	if err := websocket.Message.Receive(ws, &j); err != nil { | 	var length uint32 | ||||||
| 		log.Println("Command receive failed: " + err.Error()) | 	if err := binary.Read(r, binary.BigEndian, &length); err != nil { | ||||||
| 		return false | 		log.Println("Event receive failed: " + err.Error()) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	b := make([]byte, length) | ||||||
|  | 	if _, err := io.ReadFull(r, b); err != nil { | ||||||
|  | 		log.Println("Event receive failed: " + err.Error()) | ||||||
|  | 		return nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	log.Printf("?> %s\n", j) | 	log.Printf("<? %v\n", b) | ||||||
| 
 | 
 | ||||||
|  | 	var m RelayEventMessage | ||||||
|  | 	if after, ok := m.ConsumeFrom(b); !ok { | ||||||
|  | 		log.Println("Event deserialization failed") | ||||||
|  | 		return nil | ||||||
|  | 	} else if len(after) != 0 { | ||||||
|  | 		log.Println("Event deserialization failed: trailing data") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	j, err := m.MarshalJSON() | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println("Event marshalling failed: " + err.Error()) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return j | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func relayMakeReceiver(ctx context.Context, conn net.Conn) <-chan []byte { | ||||||
|  | 	p := make(chan []byte, 1) | ||||||
|  | 	r := bufio.NewReader(conn) | ||||||
|  | 	go func() { | ||||||
|  | 		defer close(p) | ||||||
|  | 		for { | ||||||
|  | 			j := relayReadJSON(r) | ||||||
|  | 			if j == nil { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			select { | ||||||
|  | 			case p <- j: | ||||||
|  | 			case <-ctx.Done(): | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  | 	return p | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func relayWriteJSON(conn net.Conn, j []byte) bool { | ||||||
| 	var m RelayCommandMessage | 	var m RelayCommandMessage | ||||||
| 	if err := json.Unmarshal([]byte(j), &m); err != nil { | 	if err := json.Unmarshal(j, &m); err != nil { | ||||||
| 		log.Println("Command unmarshalling failed: " + err.Error()) | 		log.Println("Command unmarshalling failed: " + err.Error()) | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| @ -56,46 +101,34 @@ func clientToRelay( | |||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func relayToClient( | // ----------------------------------------------------------------------------- | ||||||
| 	ctx context.Context, ws *websocket.Conn, conn net.Conn) bool { |  | ||||||
| 	var length uint32 |  | ||||||
| 	if err := binary.Read(conn, binary.BigEndian, &length); err != nil { |  | ||||||
| 		log.Println("Event receive failed: " + err.Error()) |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	b := make([]byte, length) |  | ||||||
| 	if _, err := io.ReadFull(conn, b); err != nil { |  | ||||||
| 		log.Println("Event receive failed: " + err.Error()) |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	log.Printf("<? %v\n", b) | func clientReadJSON(ctx context.Context, ws *websocket.Conn) []byte { | ||||||
| 
 | 	t, j, err := ws.Read(ctx) | ||||||
| 	var m RelayEventMessage |  | ||||||
| 	if after, ok := m.ConsumeFrom(b); !ok { |  | ||||||
| 		log.Println("Event deserialization failed") |  | ||||||
| 		return false |  | ||||||
| 	} else if len(after) != 0 { |  | ||||||
| 		log.Println("Event deserialization failed: trailing data") |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	j, err := json.Marshal(&m) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Println("Event marshalling failed: " + err.Error()) | 		log.Println("Command receive failed: " + err.Error()) | ||||||
| 		return false | 		return nil | ||||||
| 	} | 	} | ||||||
| 	if err := websocket.Message.Send(ws, string(j)); err != nil { | 	if t != websocket.MessageText { | ||||||
|  | 		log.Println( | ||||||
|  | 			"Command receive failed: " + "binary messages are not supported") | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	log.Printf("?> %s\n", j) | ||||||
|  | 	return j | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func clientWriteJSON(ctx context.Context, ws *websocket.Conn, j []byte) bool { | ||||||
|  | 	if err := ws.Write(ctx, websocket.MessageText, j); err != nil { | ||||||
| 		log.Println("Event send failed: " + err.Error()) | 		log.Println("Event send failed: " + err.Error()) | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	log.Printf("<- %s\n", j) | 	log.Printf("<- %s\n", j) | ||||||
| 	return true | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func errorToClient(ws *websocket.Conn, err error) bool { | func clientWriteError(ctx context.Context, ws *websocket.Conn, err error) bool { | ||||||
| 	j, err := json.Marshal(&RelayEventMessage{ | 	j, err := (&RelayEventMessage{ | ||||||
| 		EventSeq: 0, | 		EventSeq: 0, | ||||||
| 		Data: RelayEventData{ | 		Data: RelayEventData{ | ||||||
| 			Interface: RelayEventDataError{ | 			Interface: RelayEventDataError{ | ||||||
| @ -104,40 +137,67 @@ func errorToClient(ws *websocket.Conn, err error) bool { | |||||||
| 				Error:      err.Error(), | 				Error:      err.Error(), | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	}) | 	}).MarshalJSON() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Println("Event marshalling failed: " + err.Error()) | 		log.Println("Event marshalling failed: " + err.Error()) | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| 	if err := websocket.Message.Send(ws, string(j)); err != nil { | 	return clientWriteJSON(ctx, ws, j) | ||||||
| 		log.Println("Event send failed: " + err.Error()) |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
| 	return true |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func handleWebSocket(ws *websocket.Conn) { | func handleWS(w http.ResponseWriter, r *http.Request) { | ||||||
| 	conn, err := net.Dial("tcp", addressConnect) | 	ws, err := websocket.Accept(w, r, &websocket.AcceptOptions{ | ||||||
|  | 		InsecureSkipVerify: true, | ||||||
|  | 		// Note that Safari can be broken with compression. | ||||||
|  | 		CompressionMode: websocket.CompressionContextTakeover, | ||||||
|  | 		// This is for the payload; set higher to avoid overhead. | ||||||
|  | 		CompressionThreshold: 64 << 10, | ||||||
|  | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		errorToClient(ws, err) | 		log.Println("Client rejected: " + err.Error()) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  | 	defer ws.Close(websocket.StatusGoingAway, "Goodbye") | ||||||
|  | 
 | ||||||
|  | 	ctx, cancel := context.WithCancel(r.Context()) | ||||||
|  | 	defer cancel() | ||||||
|  | 
 | ||||||
|  | 	conn, err := net.Dial("tcp", addressConnect) | ||||||
|  | 	if err != nil { | ||||||
|  | 		clientWriteError(ctx, ws, err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	defer conn.Close() | ||||||
| 
 | 
 | ||||||
| 	// We don't need to intervene, so it's just two separate pipes so far. | 	// We don't need to intervene, so it's just two separate pipes so far. | ||||||
| 	ctx, cancel := context.WithCancel(ws.Request().Context()) | 	// However, to decrease latencies, events are received and decoded | ||||||
|  | 	// in parallel to their sending. | ||||||
|  | 	relayJSON := relayMakeReceiver(ctx, conn) | ||||||
| 	go func() { | 	go func() { | ||||||
| 		for clientToRelay(ctx, ws, conn) { | 		defer cancel() | ||||||
|  | 		for { | ||||||
|  | 			j := clientReadJSON(ctx, ws) | ||||||
|  | 			if j == nil { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			relayWriteJSON(conn, j) | ||||||
| 		} | 		} | ||||||
| 		cancel() |  | ||||||
| 	}() | 	}() | ||||||
| 	go func() { | 	go func() { | ||||||
| 		for relayToClient(ctx, ws, conn) { | 		defer cancel() | ||||||
|  | 		for { | ||||||
|  | 			j, ok := <-relayJSON | ||||||
|  | 			if !ok { | ||||||
|  | 				return | ||||||
|  | 			} | ||||||
|  | 			clientWriteJSON(ctx, ws, j) | ||||||
| 		} | 		} | ||||||
| 		cancel() |  | ||||||
| 	}() | 	}() | ||||||
| 	<-ctx.Done() | 	<-ctx.Done() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // ----------------------------------------------------------------------------- | ||||||
|  | 
 | ||||||
| var staticHandler = http.FileServer(http.Dir(".")) | var staticHandler = http.FileServer(http.Dir(".")) | ||||||
| 
 | 
 | ||||||
| var page = template.Must(template.New("/").Parse(`<!DOCTYPE html> | var page = template.Must(template.New("/").Parse(`<!DOCTYPE html> | ||||||
| @ -184,7 +244,7 @@ func main() { | |||||||
| 		addressWS = os.Args[3] | 		addressWS = os.Args[3] | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	http.Handle("/ws", websocket.Handler(handleWebSocket)) | 	http.Handle("/ws", http.HandlerFunc(handleWS)) | ||||||
| 	http.Handle("/", http.HandlerFunc(handleDefault)) | 	http.Handle("/", http.HandlerFunc(handleDefault)) | ||||||
| 
 | 
 | ||||||
| 	s := &http.Server{ | 	s := &http.Server{ | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user