gitee.com/liuxuezhan/go-micro-v1.18.0@v1.0.0/api/handler/broker/broker.go (about)

     1  // Package broker provides a go-micro/broker handler
     2  package broker
     3  
     4  import (
     5  	"encoding/json"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/url"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/gorilla/websocket"
    14  	"gitee.com/liuxuezhan/go-micro-v1.18.0/api/handler"
    15  	"gitee.com/liuxuezhan/go-micro-v1.18.0/broker"
    16  	"gitee.com/liuxuezhan/go-micro-v1.18.0/util/log"
    17  )
    18  
    19  const (
    20  	Handler = "broker"
    21  
    22  	pingTime      = (readDeadline * 9) / 10
    23  	readLimit     = 16384
    24  	readDeadline  = 60 * time.Second
    25  	writeDeadline = 10 * time.Second
    26  )
    27  
    28  type brokerHandler struct {
    29  	opts handler.Options
    30  	u    websocket.Upgrader
    31  }
    32  
    33  type conn struct {
    34  	b     broker.Broker
    35  	cType string
    36  	topic string
    37  	queue string
    38  	exit  chan bool
    39  
    40  	sync.Mutex
    41  	ws *websocket.Conn
    42  }
    43  
    44  var (
    45  	once        sync.Once
    46  	contentType = "text/plain"
    47  )
    48  
    49  func checkOrigin(r *http.Request) bool {
    50  	origin := r.Header["Origin"]
    51  	if len(origin) == 0 {
    52  		return true
    53  	}
    54  	u, err := url.Parse(origin[0])
    55  	if err != nil {
    56  		return false
    57  	}
    58  	return u.Host == r.Host
    59  }
    60  
    61  func (c *conn) close() {
    62  	select {
    63  	case <-c.exit:
    64  		return
    65  	default:
    66  		close(c.exit)
    67  	}
    68  }
    69  
    70  func (c *conn) readLoop() {
    71  	defer func() {
    72  		c.close()
    73  		c.ws.Close()
    74  	}()
    75  
    76  	// set read limit/deadline
    77  	c.ws.SetReadLimit(readLimit)
    78  	c.ws.SetReadDeadline(time.Now().Add(readDeadline))
    79  
    80  	// set close handler
    81  	ch := c.ws.CloseHandler()
    82  	c.ws.SetCloseHandler(func(code int, text string) error {
    83  		err := ch(code, text)
    84  		c.close()
    85  		return err
    86  	})
    87  
    88  	// set pong handler
    89  	c.ws.SetPongHandler(func(string) error {
    90  		c.ws.SetReadDeadline(time.Now().Add(readDeadline))
    91  		return nil
    92  	})
    93  
    94  	for {
    95  		_, message, err := c.ws.ReadMessage()
    96  		if err != nil {
    97  			return
    98  		}
    99  		c.b.Publish(c.topic, &broker.Message{
   100  			Header: map[string]string{"Content-Type": c.cType},
   101  			Body:   message,
   102  		})
   103  	}
   104  }
   105  
   106  func (c *conn) write(mType int, data []byte) error {
   107  	c.Lock()
   108  	c.ws.SetWriteDeadline(time.Now().Add(writeDeadline))
   109  	err := c.ws.WriteMessage(mType, data)
   110  	c.Unlock()
   111  	return err
   112  }
   113  
   114  func (c *conn) writeLoop() {
   115  	ticker := time.NewTicker(pingTime)
   116  
   117  	var opts []broker.SubscribeOption
   118  
   119  	if len(c.queue) > 0 {
   120  		opts = append(opts, broker.Queue(c.queue))
   121  	}
   122  
   123  	subscriber, err := c.b.Subscribe(c.topic, func(p broker.Event) error {
   124  		b, err := json.Marshal(p.Message())
   125  		if err != nil {
   126  			return nil
   127  		}
   128  		return c.write(websocket.TextMessage, b)
   129  	}, opts...)
   130  
   131  	defer func() {
   132  		subscriber.Unsubscribe()
   133  		ticker.Stop()
   134  		c.ws.Close()
   135  	}()
   136  
   137  	if err != nil {
   138  		log.Log(err.Error())
   139  		return
   140  	}
   141  
   142  	for {
   143  		select {
   144  		case <-ticker.C:
   145  			if err := c.write(websocket.PingMessage, []byte{}); err != nil {
   146  				return
   147  			}
   148  		case <-c.exit:
   149  			return
   150  		}
   151  	}
   152  }
   153  
   154  func (b *brokerHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   155  	br := b.opts.Service.Client().Options().Broker
   156  
   157  	// Setup the broker
   158  	once.Do(func() {
   159  		br.Init()
   160  		br.Connect()
   161  	})
   162  
   163  	// Parse
   164  	r.ParseForm()
   165  	topic := r.Form.Get("topic")
   166  
   167  	// Can't do anything without a topic
   168  	if len(topic) == 0 {
   169  		http.Error(w, "Topic not specified", 400)
   170  		return
   171  	}
   172  
   173  	// Post assumed to be Publish
   174  	if r.Method == "POST" {
   175  		// Create a broker message
   176  		msg := &broker.Message{
   177  			Header: make(map[string]string),
   178  		}
   179  
   180  		// Set header
   181  		for k, v := range r.Header {
   182  			msg.Header[k] = strings.Join(v, ", ")
   183  		}
   184  
   185  		// Read body
   186  		b, err := ioutil.ReadAll(r.Body)
   187  		if err != nil {
   188  			http.Error(w, err.Error(), 500)
   189  			return
   190  		}
   191  
   192  		// Set body
   193  		msg.Body = b
   194  
   195  		// Publish
   196  		br.Publish(topic, msg)
   197  		return
   198  	}
   199  
   200  	// now back to our regularly scheduled programming
   201  
   202  	if r.Method != "GET" {
   203  		http.Error(w, "Method not allowed", 405)
   204  		return
   205  	}
   206  
   207  	queue := r.Form.Get("queue")
   208  
   209  	ws, err := b.u.Upgrade(w, r, nil)
   210  	if err != nil {
   211  		log.Log(err.Error())
   212  		return
   213  	}
   214  
   215  	cType := r.Header.Get("Content-Type")
   216  	if len(cType) == 0 {
   217  		cType = contentType
   218  	}
   219  
   220  	c := &conn{
   221  		b:     br,
   222  		cType: cType,
   223  		topic: topic,
   224  		queue: queue,
   225  		exit:  make(chan bool),
   226  		ws:    ws,
   227  	}
   228  
   229  	go c.writeLoop()
   230  	c.readLoop()
   231  }
   232  
   233  func (b *brokerHandler) String() string {
   234  	return "broker"
   235  }
   236  
   237  func NewHandler(opts ...handler.Option) handler.Handler {
   238  	return &brokerHandler{
   239  		u: websocket.Upgrader{
   240  			CheckOrigin: func(r *http.Request) bool {
   241  				return true
   242  			},
   243  			ReadBufferSize:  1024,
   244  			WriteBufferSize: 1024,
   245  		},
   246  		opts: handler.NewOptions(opts...),
   247  	}
   248  }
   249  
   250  func WithCors(cors map[string]bool, opts ...handler.Option) handler.Handler {
   251  	return &brokerHandler{
   252  		u: websocket.Upgrader{
   253  			CheckOrigin: func(r *http.Request) bool {
   254  				if origin := r.Header.Get("Origin"); cors[origin] {
   255  					return true
   256  				} else if len(origin) > 0 && cors["*"] {
   257  					return true
   258  				} else if checkOrigin(r) {
   259  					return true
   260  				}
   261  				return false
   262  			},
   263  			ReadBufferSize:  1024,
   264  			WriteBufferSize: 1024,
   265  		},
   266  		opts: handler.NewOptions(opts...),
   267  	}
   268  }