Переглянути джерело

codec: streamline handling of doing something at the end of an encode.

For example, json handle needs to add some whitespace at the end of an encode,
especially if it was a number being encoded.

For rpc, we will now leverage that general support, instead of a one-off
support done for rpc (via rpcEncodeTerminator interface)
Ugorji Nwoke 8 роки тому
батько
коміт
69722bf3a2
4 змінених файлів з 96 додано та 29 видалено
  1. 22 0
      codec/codec_test.go
  2. 3 0
      codec/encode.go
  3. 50 15
      codec/json.go
  4. 21 14
      codec/rpc.go

+ 22 - 0
codec/codec_test.go

@@ -951,6 +951,11 @@ func testCodecRpcOne(t *testing.T, rr Rpc, h Handle, doRequest bool, exitSleepMs
 		logT(t, "EXPECTED. set recoverPanicToErr=true, since rpc needs EOF")
 		failT(t)
 	}
+
+	if jsonH, ok := h.(*JsonHandle); ok && !jsonH.TermWhitespace {
+		jsonH.TermWhitespace = true
+		defer func() { jsonH.TermWhitespace = false }()
+	}
 	srv := rpc.NewServer()
 	srv.Register(testRpcInt)
 	ln, err := net.Listen("tcp", "127.0.0.1:0")
@@ -2184,3 +2189,20 @@ func TestBufioDecReader(t *testing.T) {
 //
 //  Add negative tests for failure conditions:
 //   - bad input with large array length prefix
+//
+//  More tests:
+//  - length of containers (array, string, bytes, map):
+//    len = 0
+//    maxUint16 < len < maxuint32
+//    len > maxuint32
+//  - large numbers: in every range: up to maxuint8, maxuint16, maxuint32, maxuint64
+//    int64
+//    uint64
+//  - standard numbers:
+//    0, -1, 1, +inf, -inf, 0.0f, etc
+//  - (encode ext, encode raw ext, etc)
+//    (include extensions)
+//  - tracking:
+//    z.trb: track, stop track, check
+//  - test with diff: mapType and sliceType, and decodeNaked
+//  - decodeAsNil: for maps, slices, etc

+ 3 - 0
codec/encode.go

@@ -76,6 +76,7 @@ type encDriver interface {
 	//encStringRunes(c charEncoding, v []rune)
 
 	reset()
+	atEndOfEncode()
 }
 
 type ioEncStringWriter interface {
@@ -102,6 +103,7 @@ func (_ encDriverNoopContainerWriter) WriteMapStart(length int)   {}
 func (_ encDriverNoopContainerWriter) WriteMapElemKey()           {}
 func (_ encDriverNoopContainerWriter) WriteMapElemValue()         {}
 func (_ encDriverNoopContainerWriter) WriteMapEnd()               {}
+func (_ encDriverNoopContainerWriter) atEndOfEncode()             {}
 
 // type ioEncWriterWriter interface {
 // 	WriteByte(c byte) error
@@ -1220,6 +1222,7 @@ func (e *Encoder) MustEncode(v interface{}) {
 		panic(e.err)
 	}
 	e.encode(v)
+	e.e.atEndOfEncode()
 	e.w.atEndOfEncode()
 }
 

+ 50 - 15
codec/json.go

@@ -63,6 +63,7 @@ var (
 	jsonCharSafeSet       bitset128
 	jsonCharWhitespaceSet bitset256
 	jsonNumSet            bitset256
+	// jsonIsFloatSet        bitset256
 
 	jsonU4Set [256]byte
 )
@@ -134,6 +135,10 @@ func init() {
 		default:
 			jsonU4Set[i] = jsonU4SetErrVal
 		}
+		// switch i = byte(j); i {
+		// case 'e', 'E', '.':
+		// 	jsonIsFloatSet.set(i)
+		// }
 	}
 	// jsonU4Set[255] = jsonU4SetErrVal
 }
@@ -305,10 +310,11 @@ func (e *jsonEncDriver) EncodeFloat64(f float64) {
 
 func (e *jsonEncDriver) encodeFloat(f float64, numbits int) {
 	x := strconv.AppendFloat(e.b[:0], f, 'G', -1, numbits)
-	e.w.writeb(x)
-	if bytes.IndexByte(x, 'E') == -1 && bytes.IndexByte(x, '.') == -1 {
-		e.w.writen2('.', '0')
+	// if bytes.IndexByte(x, 'E') == -1 && bytes.IndexByte(x, '.') == -1 {
+	if !jsonIsFloatBytesB2(x) {
+		x = append(x, '.', '0')
 	}
+	e.w.writeb(x)
 }
 
 func (e *jsonEncDriver) EncodeInt(v int64) {
@@ -451,6 +457,16 @@ func (e *jsonEncDriver) quoteStr(s string) {
 	w.writen1('"')
 }
 
+func (e *jsonEncDriver) atEndOfEncode() {
+	if e.h.TermWhitespace {
+		if e.d {
+			e.w.writen1('\n')
+		} else {
+			e.w.writen1(' ')
+		}
+	}
+}
+
 type jsonDecDriver struct {
 	noBuiltInTypes
 	d *Decoder
@@ -981,7 +997,7 @@ func (d *jsonDecDriver) DecodeNaked() {
 		if len(bs) == 0 {
 			d.d.errorf("json: decode number from empty string")
 			return
-		} else if d.h.PreferFloat || jsonIsFloatBytes(bs) { // bytes.IndexByte(bs, '.') != -1 ||...
+		} else if d.h.PreferFloat || jsonIsFloatBytesB3(bs) { // bytes.IndexByte(bs, '.') != -1 ||...
 			// } else if d.h.PreferFloat || bytes.ContainsAny(bs, ".eE") {
 			z.v = valueTypeFloat
 			z.f, err = strconv.ParseFloat(stringView(bs), 64)
@@ -1065,6 +1081,13 @@ type JsonHandle struct {
 	// If not set, we will examine the characters of the number and decode as an
 	// integer type if it doesn't have any of the characters [.eE].
 	PreferFloat bool
+
+	// TermWhitespace says that we add a whitespace character
+	// at the end of an encoding.
+	//
+	// The whitespace is important, especially if using numbers in a context
+	// where multiple items are written to a stream.
+	TermWhitespace bool
 }
 
 func (h *JsonHandle) hasElemSeparators() bool { return true }
@@ -1118,20 +1141,32 @@ func (d *jsonDecDriver) reset() {
 	// d.n.reset()
 }
 
-func jsonIsFloatBytes(bs []byte) bool {
-	for _, v := range bs {
-		if v == '.' || v == 'e' || v == 'E' {
-			return true
-		}
-	}
-	return false
-}
+// func jsonIsFloatBytes(bs []byte) bool {
+// 	for _, v := range bs {
+// 		// if v == '.' || v == 'e' || v == 'E' {
+// 		if jsonIsFloatSet.isset(v) {
+// 			return true
+// 		}
+// 	}
+// 	return false
+// }
 
-var jsonEncodeTerminate = []byte{' '}
+func jsonIsFloatBytesB2(bs []byte) bool {
+	return bytes.IndexByte(bs, '.') != -1 ||
+		bytes.IndexByte(bs, 'E') != -1
+}
 
-func (h *JsonHandle) rpcEncodeTerminate() []byte {
-	return jsonEncodeTerminate
+func jsonIsFloatBytesB3(bs []byte) bool {
+	return bytes.IndexByte(bs, '.') != -1 ||
+		bytes.IndexByte(bs, 'E') != -1 ||
+		bytes.IndexByte(bs, 'e') != -1
 }
 
+// var jsonEncodeTerminate = []byte{' '}
+
+// func (h *JsonHandle) rpcEncodeTerminate() []byte {
+// 	return jsonEncodeTerminate
+// }
+
 var _ decDriver = (*jsonDecDriver)(nil)
 var _ encDriver = (*jsonEncDriver)(nil)

+ 21 - 14
codec/rpc.go

@@ -5,18 +5,19 @@ package codec
 
 import (
 	"bufio"
+	"errors"
 	"io"
 	"net/rpc"
 	"sync"
 )
 
-// rpcEncodeTerminator allows a handler specify a []byte terminator to send after each Encode.
-//
-// Some codecs like json need to put a space after each encoded value, to serve as a
-// delimiter for things like numbers (else json codec will continue reading till EOF).
-type rpcEncodeTerminator interface {
-	rpcEncodeTerminate() []byte
-}
+// // rpcEncodeTerminator allows a handler specify a []byte terminator to send after each Encode.
+// //
+// // Some codecs like json need to put a space after each encoded value, to serve as a
+// // delimiter for things like numbers (else json codec will continue reading till EOF).
+// type rpcEncodeTerminator interface {
+// 	rpcEncodeTerminate() []byte
+// }
 
 // Rpc provides a rpc Server or Client Codec for rpc communication.
 type Rpc interface {
@@ -52,6 +53,12 @@ type rpcCodec struct {
 func newRPCCodec(conn io.ReadWriteCloser, h Handle) rpcCodec {
 	bw := bufio.NewWriter(conn)
 	br := bufio.NewReader(conn)
+
+	// defensive: ensure that jsonH has TermWhitespace turned on.
+	if jsonH, ok := h.(*JsonHandle); ok && !jsonH.TermWhitespace {
+		panic(errors.New("rpc requires a JsonHandle with TermWhitespace set to true"))
+	}
+
 	return rpcCodec{
 		rwc: conn,
 		bw:  bw,
@@ -77,17 +84,17 @@ func (c *rpcCodec) write(obj1, obj2 interface{}, writeObj2, doFlush bool) (err e
 	if err = c.enc.Encode(obj1); err != nil {
 		return
 	}
-	t, tOk := c.h.(rpcEncodeTerminator)
-	if tOk {
-		c.bw.Write(t.rpcEncodeTerminate())
-	}
+	// t, tOk := c.h.(rpcEncodeTerminator)
+	// if tOk {
+	// 	c.bw.Write(t.rpcEncodeTerminate())
+	// }
 	if writeObj2 {
 		if err = c.enc.Encode(obj2); err != nil {
 			return
 		}
-		if tOk {
-			c.bw.Write(t.rpcEncodeTerminate())
-		}
+		// if tOk {
+		// 	c.bw.Write(t.rpcEncodeTerminate())
+		// }
 	}
 	if doFlush {
 		return c.bw.Flush()