summaryrefslogtreecommitdiffstats
path: root/message.go
diff options
context:
space:
mode:
Diffstat (limited to 'message.go')
-rw-r--r--message.go176
1 files changed, 176 insertions, 0 deletions
diff --git a/message.go b/message.go
new file mode 100644
index 0000000..eb3d2b9
--- /dev/null
+++ b/message.go
@@ -0,0 +1,176 @@
+package cnp
+
+import (
+ "bufio"
+ "bytes"
+ "io"
+ "io/ioutil"
+ "strings"
+)
+
+// Message represents a CNP message.
+type Message struct {
+ Header Header
+ Body io.Reader
+ closer io.Closer
+}
+
+// NewMessage creates a new CNP message.
+//
+// This method also calls Message.TryComputeLength().
+func NewMessage(intent string, body io.Reader) *Message {
+ msg := &Message{
+ Header: NewHeader(intent, nil),
+ Body: body,
+ }
+
+ msg.TryComputeLength()
+
+ return msg
+}
+
+// ParseMessage parses a CNP message.
+//
+// The message's Body field is set to a bufio.Reader wrapping r. If r is a
+// io.Closer, it is also stored separately for usage with Message.Close().
+func ParseMessage(r io.Reader) (*Message, error) {
+ br := bufio.NewReader(r)
+
+ line, err := readLimitedLine(br, MaxHeaderLength)
+ if err != nil {
+ return nil, err
+ }
+
+ h, err := ParseHeader(line)
+ if err != nil {
+ return nil, err
+ }
+
+ msg := &Message{
+ Header: h,
+ Body: br,
+ }
+
+ if rc, ok := r.(io.Closer); ok {
+ msg.closer = rc
+ }
+
+ return msg, nil
+}
+
+// Close attempts to close the message body reader.
+//
+// If the message's Body field is an io.Closer, its Close() method is called.
+// If Body is not an io.Closer and the message was created with ParseMessage
+// provided with an io.Closer, the Close() method on the original reader will
+// be called.
+// Otherwise, this function does nothing.
+func (msg *Message) Close() error {
+ if rc, ok := msg.Body.(io.Closer); ok {
+ return rc.Close()
+ }
+ if msg.closer != nil {
+ return msg.closer.Close()
+ }
+ return nil
+}
+
+// ComputeLength sets the length header parameter based on the message body.
+// First, msg.TryComputeLength() is attempted; if that fails, the request is
+// fully read into a buffer and msg.Body is set to a bytes.Reader.
+func (msg *Message) ComputeLength() error {
+ if !msg.TryComputeLength() {
+ buf, err := ioutil.ReadAll(msg.Body)
+ if len(buf) > 0 {
+ msg.Body = bytes.NewReader(buf)
+ msg.SetLength(int64(len(buf)))
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// TryComputeLength sets the length header parameter to the length of the
+// message body if it's one of *bytes.Buffer, *bytes.Reader or *strings.Reader
+// and returns true. If msg.Body is nil, the length parameter is unset and the
+// function returns true. Otherwise, false is returned.
+func (msg *Message) TryComputeLength() bool {
+ switch v := msg.Body.(type) {
+ case *bytes.Buffer:
+ msg.SetLength(int64(v.Len()))
+ case *bytes.Reader:
+ msg.SetLength(int64(v.Len()))
+ case *strings.Reader:
+ msg.SetLength(int64(v.Len()))
+ case nil:
+ msg.SetLength(0)
+ default:
+ return false
+ }
+ return true
+}
+
+// SetLength sets the length header parameter to n.
+func (msg *Message) SetLength(n int64) {
+ if n == 0 {
+ msg.SetParam("length", "")
+ } else {
+ setInt(msg, "length", n)
+ }
+}
+
+// Length gets the length header parameter (or 0 if it's not set or invalid).
+func (msg *Message) Length() int64 {
+ n, err := getInt(msg, "length")
+ if err != nil {
+ return 0
+ }
+ return n
+}
+
+// Param retrieves a header parameter.
+func (msg *Message) Param(key string) string {
+ return msg.Header.Parameters[key]
+}
+
+// SetParam sets a header parameter. If the value is empty, the parameter is
+// unset.
+func (msg *Message) SetParam(key, value string) {
+ if len(value) == 0 {
+ delete(msg.Header.Parameters, key)
+ } else {
+ msg.Header.Parameters[key] = value
+ }
+}
+
+// Intent retrieves the message header intent.
+func (msg *Message) Intent() string {
+ return msg.Header.Intent
+}
+
+// SetIntent sets the message header intent.
+func (msg *Message) SetIntent(s string) {
+ msg.Header.Intent = s
+}
+
+// Validate validates the message header parameter value format (length).
+func (msg *Message) Validate() error {
+ _, err := getInt(msg, "length")
+ return err
+}
+
+// Write writes the message header and body to w.
+func (msg *Message) Write(w io.Writer) error {
+ if err := msg.Header.Write(w); err != nil {
+ return err
+ }
+ if msg.Body != nil {
+ if _, err := io.Copy(w, msg.Body); err != nil {
+ return err
+ }
+ }
+ return nil
+}