Compare commits
	
		
			No commits in common. "92f2f6895b41ecb60e3aaf59e3ea5bc20d9cd314" and "a551e911ab54f5b1f56f837c3ebed42ef86fb4a1" have entirely different histories.
		
	
	
		
			92f2f6895b
			...
			a551e911ab
		
	
		
| @ -206,14 +206,13 @@ 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 []byte(`\"` + v.String() + `\"`), nil" | 	print "\t\treturn json.Marshal(v.String())" | ||||||
| 	print "\t}" | 	print "\t}" | ||||||
| 	print "\treturn json.Marshal(int(v))" | 	print "\treturn json.Marshal(int(v))" | ||||||
| 	print "}" | 	print "}" | ||||||
| @ -253,50 +252,7 @@ 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"]] | ||||||
| @ -347,8 +303,6 @@ 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"]] \ | ||||||
| @ -361,16 +315,6 @@ 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" | ||||||
| @ -418,15 +362,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) { | ||||||
| @ -437,9 +381,8 @@ 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 u.Interface.(json.Marshaler).MarshalJSON()" | 	print "\treturn json.Marshal(u.Interface)" | ||||||
| 	print "}" | 	print "}" | ||||||
| 	print "" | 	print "" | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								xC.c
									
									
									
									
									
								
							| @ -1541,7 +1541,6 @@ 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
 | ||||||
| @ -1550,7 +1549,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, .clean = true }; | 	struct formatter self = { .ctx = ctx, .s = s }; | ||||||
| 	self.items = xcalloc (sizeof *self.items, (self.items_alloc = 16)); | 	self.items = xcalloc (sizeof *self.items, (self.items_alloc = 16)); | ||||||
| 	return self; | 	return self; | ||||||
| } | } | ||||||
| @ -3812,7 +3811,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 in an unknown encoding
 | //   #S inserts a string from the server with 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
 | ||||||
| @ -3828,17 +3827,6 @@ 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,9 +2,4 @@ module janouch.name/xK/xP | |||||||
| 
 | 
 | ||||||
| go 1.18 | go 1.18 | ||||||
| 
 | 
 | ||||||
| require nhooyr.io/websocket v1.8.7 | require golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d | ||||||
| 
 |  | ||||||
| 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,62 +1,2 @@ | |||||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= | ||||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||||
| 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,6 +56,8 @@ 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" | ||||||
| 
 | 
 | ||||||
| @ -83,8 +85,11 @@ class RelayRpc extends EventTarget { | |||||||
| 			if (typeof e.event !== 'string') | 			if (typeof e.event !== 'string') | ||||||
| 				throw "Invalid event tag" | 				throw "Invalid event tag" | ||||||
| 
 | 
 | ||||||
| 			e.eventSeq = message.eventSeq | 			this.dispatchEvent(new CustomEvent( | ||||||
| 			this.dispatchEvent(new CustomEvent('event', {detail: e})) | 				e.event, {detail: {eventSeq: message.eventSeq, ...e}})) | ||||||
|  | 
 | ||||||
|  | 			// Minor abstraction layering violation.
 | ||||||
|  | 			m.redraw() | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -181,7 +186,6 @@ 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 | ||||||
| @ -246,23 +250,14 @@ rpc.addEventListener('close', event => { | |||||||
| 	m.redraw() | 	m.redraw() | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('event', event => { | rpc.addEventListener('Ping', event => { | ||||||
| 	const handler = rpcEventHandlers[event.detail.event] | 	rpc.send({command: 'PingResponse', eventSeq: event.detail.eventSeq}) | ||||||
| 	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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['BufferUpdate'] = e => { | rpc.addEventListener('BufferUpdate', event => { | ||||||
| 	let b = buffers.get(e.bufferName) | 	let e = event.detail, b = buffers.get(e.bufferName) | ||||||
| 	if (b === undefined) { | 	if (b === undefined) { | ||||||
| 		buffers.set(e.bufferName, (b = { | 		buffers.set(e.bufferName, (b = { | ||||||
| 			lines: [], | 			lines: [], | ||||||
| @ -275,36 +270,38 @@ rpcEventHandlers['BufferUpdate'] = e => { | |||||||
| 	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) | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['BufferStats'] = e => { | rpc.addEventListener('BufferStats', event => { | ||||||
| 	let b = buffers.get(e.bufferName) | 	let e = event.detail, 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 | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['BufferRename'] = e => { | rpc.addEventListener('BufferRename', event => { | ||||||
|  | 	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) | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['BufferRemove'] = e => { | rpc.addEventListener('BufferRemove', event => { | ||||||
|  | 	let e = event.detail | ||||||
| 	buffers.delete(e.bufferName) | 	buffers.delete(e.bufferName) | ||||||
| 	if (e.bufferName === bufferLast) | 	if (e.bufferName === bufferLast) | ||||||
| 		bufferLast = undefined | 		bufferLast = undefined | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['BufferActivate'] = e => { | rpc.addEventListener('BufferActivate', event => { | ||||||
| 	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 b = buffers.get(e.bufferName) | 	let e = event.detail, b = buffers.get(e.bufferName) | ||||||
| 	bufferCurrent = e.bufferName | 	bufferCurrent = e.bufferName | ||||||
| 	bufferLog = undefined | 	bufferLog = undefined | ||||||
| 	bufferAutoscroll = true | 	bufferAutoscroll = true | ||||||
| @ -331,12 +328,11 @@ rpcEventHandlers['BufferActivate'] = e => { | |||||||
| 		textarea.value = b.input | 		textarea.value = b.input | ||||||
| 		textarea.setSelectionRange(b.inputStart, b.inputEnd, b.inputDirection) | 		textarea.setSelectionRange(b.inputStart, b.inputEnd, b.inputDirection) | ||||||
| 	} | 	} | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['BufferLine'] = e => { | rpc.addEventListener('BufferLine', event => { | ||||||
| 	let b = buffers.get(e.bufferName), line = {...e} | 	let e = event.detail, 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 | ||||||
| @ -376,31 +372,33 @@ rpcEventHandlers['BufferLine'] = e => { | |||||||
| 		if (!visible) | 		if (!visible) | ||||||
| 			b.highlighted = true | 			b.highlighted = true | ||||||
| 	} | 	} | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['BufferClear'] = e => { | rpc.addEventListener('BufferClear', event => { | ||||||
| 	let b = buffers.get(e.bufferName) | 	let e = event.detail, b = buffers.get(e.bufferName) | ||||||
| 	if (b !== undefined) | 	if (b !== undefined) | ||||||
| 		b.lines.length = 0 | 		b.lines.length = 0 | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| // ~~~ Server events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | // ~~~ Server events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['ServerUpdate'] = e => { | rpc.addEventListener('ServerUpdate', event => { | ||||||
| 	let s = servers.get(e.serverName) | 	let e = event.detail, 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 | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['ServerRename'] = e => { | rpc.addEventListener('ServerRename', event => { | ||||||
|  | 	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) | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| rpcEventHandlers['ServerRemove'] = e => { | rpc.addEventListener('ServerRemove', event => { | ||||||
|  | 	let e = event.detail | ||||||
| 	servers.delete(e.serverName) | 	servers.delete(e.serverName) | ||||||
| } | }) | ||||||
| 
 | 
 | ||||||
| // --- Colours -----------------------------------------------------------------
 | // --- Colours -----------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										166
									
								
								xP/xP.go
									
									
									
									
									
								
							
							
						
						
									
										166
									
								
								xP/xP.go
									
									
									
									
									
								
							| @ -4,7 +4,6 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bufio" |  | ||||||
| 	"context" | 	"context" | ||||||
| 	"encoding/binary" | 	"encoding/binary" | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| @ -17,7 +16,7 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"nhooyr.io/websocket" | 	"golang.org/x/net/websocket" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var ( | var ( | ||||||
| @ -26,62 +25,18 @@ var ( | |||||||
| 	addressWS      string | 	addressWS      string | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // ----------------------------------------------------------------------------- | func clientToRelay( | ||||||
| 
 | 	ctx context.Context, ws *websocket.Conn, conn net.Conn) bool { | ||||||
| func relayReadJSON(r io.Reader) []byte { | 	var j string | ||||||
| 	var length uint32 | 	if err := websocket.Message.Receive(ws, &j); err != nil { | ||||||
| 	if err := binary.Read(r, binary.BigEndian, &length); err != nil { | 		log.Println("Command receive failed: " + err.Error()) | ||||||
| 		log.Println("Event receive failed: " + err.Error()) | 		return false | ||||||
| 		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("<? %v\n", b) | 	log.Printf("?> %s\n", j) | ||||||
| 
 | 
 | ||||||
| 	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(j, &m); err != nil { | 	if err := json.Unmarshal([]byte(j), &m); err != nil { | ||||||
| 		log.Println("Command unmarshalling failed: " + err.Error()) | 		log.Println("Command unmarshalling failed: " + err.Error()) | ||||||
| 		return false | 		return false | ||||||
| 	} | 	} | ||||||
| @ -101,34 +56,46 @@ func relayWriteJSON(conn net.Conn, j []byte) bool { | |||||||
| 	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 | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| func clientReadJSON(ctx context.Context, ws *websocket.Conn) []byte { | 	log.Printf("<? %v\n", b) | ||||||
| 	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("Command receive failed: " + err.Error()) | 		log.Println("Event marshalling failed: " + err.Error()) | ||||||
| 		return nil | 		return false | ||||||
| 	} | 	} | ||||||
| 	if t != websocket.MessageText { | 	if err := websocket.Message.Send(ws, string(j)); err != nil { | ||||||
| 		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 clientWriteError(ctx context.Context, ws *websocket.Conn, err error) bool { | func errorToClient(ws *websocket.Conn, err error) bool { | ||||||
| 	j, err := (&RelayEventMessage{ | 	j, err := json.Marshal(&RelayEventMessage{ | ||||||
| 		EventSeq: 0, | 		EventSeq: 0, | ||||||
| 		Data: RelayEventData{ | 		Data: RelayEventData{ | ||||||
| 			Interface: RelayEventDataError{ | 			Interface: RelayEventDataError{ | ||||||
| @ -137,67 +104,40 @@ func clientWriteError(ctx context.Context, 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 | ||||||
| 	} | 	} | ||||||
| 	return clientWriteJSON(ctx, ws, j) | 	if err := websocket.Message.Send(ws, string(j)); err != nil { | ||||||
|  | 		log.Println("Event send failed: " + err.Error()) | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func handleWS(w http.ResponseWriter, r *http.Request) { | func handleWebSocket(ws *websocket.Conn) { | ||||||
| 	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 { |  | ||||||
| 		log.Println("Client rejected: " + err.Error()) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	defer ws.Close(websocket.StatusGoingAway, "Goodbye") |  | ||||||
| 
 |  | ||||||
| 	ctx, cancel := context.WithCancel(r.Context()) |  | ||||||
| 	defer cancel() |  | ||||||
| 
 |  | ||||||
| 	conn, err := net.Dial("tcp", addressConnect) | 	conn, err := net.Dial("tcp", addressConnect) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		clientWriteError(ctx, ws, err) | 		errorToClient(ws, err) | ||||||
| 		return | 		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. | ||||||
| 	// However, to decrease latencies, events are received and decoded | 	ctx, cancel := context.WithCancel(ws.Request().Context()) | ||||||
| 	// in parallel to their sending. |  | ||||||
| 	relayJSON := relayMakeReceiver(ctx, conn) |  | ||||||
| 	go func() { | 	go func() { | ||||||
| 		defer cancel() | 		for clientToRelay(ctx, ws, conn) { | ||||||
| 		for { |  | ||||||
| 			j := clientReadJSON(ctx, ws) |  | ||||||
| 			if j == nil { |  | ||||||
| 				return |  | ||||||
| 			} |  | ||||||
| 			relayWriteJSON(conn, j) |  | ||||||
| 		} | 		} | ||||||
|  | 		cancel() | ||||||
| 	}() | 	}() | ||||||
| 	go func() { | 	go func() { | ||||||
| 		defer cancel() | 		for relayToClient(ctx, ws, conn) { | ||||||
| 		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> | ||||||
| @ -244,7 +184,7 @@ func main() { | |||||||
| 		addressWS = os.Args[3] | 		addressWS = os.Args[3] | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	http.Handle("/ws", http.HandlerFunc(handleWS)) | 	http.Handle("/ws", websocket.Handler(handleWebSocket)) | ||||||
| 	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