github.com/nsqio/nsq@v1.3.0/nsqd/http_test.go (about)

     1  package nsqd
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/tls"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"net"
    10  	"net/http"
    11  	"os"
    12  	"runtime"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/nsqio/go-nsq"
    20  	"github.com/nsqio/nsq/internal/http_api"
    21  	"github.com/nsqio/nsq/internal/test"
    22  	"github.com/nsqio/nsq/internal/version"
    23  	"github.com/nsqio/nsq/nsqlookupd"
    24  )
    25  
    26  type ErrMessage struct {
    27  	Message string `json:"message"`
    28  }
    29  
    30  type InfoDoc struct {
    31  	Version          string `json:"version"`
    32  	BroadcastAddress string `json:"broadcast_address"`
    33  	Hostname         string `json:"hostname"`
    34  	HTTPPort         int    `json:"http_port"`
    35  	TCPPort          int    `json:"tcp_port"`
    36  	StartTime        int64  `json:"start_time"`
    37  }
    38  
    39  func TestHTTPpub(t *testing.T) {
    40  	opts := NewOptions()
    41  	opts.Logger = test.NewTestLogger(t)
    42  	_, httpAddr, nsqd := mustStartNSQD(opts)
    43  	defer os.RemoveAll(opts.DataPath)
    44  	defer nsqd.Exit()
    45  
    46  	topicName := "test_http_pub" + strconv.Itoa(int(time.Now().Unix()))
    47  	topic := nsqd.GetTopic(topicName)
    48  
    49  	buf := bytes.NewBuffer([]byte("test message"))
    50  	url := fmt.Sprintf("http://%s/pub?topic=%s", httpAddr, topicName)
    51  	resp, err := http.Post(url, "application/octet-stream", buf)
    52  	test.Nil(t, err)
    53  	defer resp.Body.Close()
    54  	body, _ := io.ReadAll(resp.Body)
    55  	test.Equal(t, "OK", string(body))
    56  
    57  	time.Sleep(5 * time.Millisecond)
    58  
    59  	test.Equal(t, int64(1), topic.Depth())
    60  }
    61  
    62  func TestHTTPpubEmpty(t *testing.T) {
    63  	opts := NewOptions()
    64  	opts.Logger = test.NewTestLogger(t)
    65  	_, httpAddr, nsqd := mustStartNSQD(opts)
    66  	defer os.RemoveAll(opts.DataPath)
    67  	defer nsqd.Exit()
    68  
    69  	topicName := "test_http_pub_empty" + strconv.Itoa(int(time.Now().Unix()))
    70  	topic := nsqd.GetTopic(topicName)
    71  
    72  	buf := bytes.NewBuffer([]byte(""))
    73  	url := fmt.Sprintf("http://%s/pub?topic=%s", httpAddr, topicName)
    74  	resp, err := http.Post(url, "application/octet-stream", buf)
    75  	test.Nil(t, err)
    76  	defer resp.Body.Close()
    77  	body, _ := io.ReadAll(resp.Body)
    78  	test.Equal(t, 400, resp.StatusCode)
    79  	test.Equal(t, `{"message":"MSG_EMPTY"}`, string(body))
    80  
    81  	time.Sleep(5 * time.Millisecond)
    82  
    83  	test.Equal(t, int64(0), topic.Depth())
    84  }
    85  
    86  func TestHTTPmpub(t *testing.T) {
    87  	opts := NewOptions()
    88  	opts.Logger = test.NewTestLogger(t)
    89  	_, httpAddr, nsqd := mustStartNSQD(opts)
    90  	defer os.RemoveAll(opts.DataPath)
    91  	defer nsqd.Exit()
    92  
    93  	topicName := "test_http_mpub" + strconv.Itoa(int(time.Now().Unix()))
    94  	topic := nsqd.GetTopic(topicName)
    95  
    96  	msg := []byte("test message")
    97  	msgs := make([][]byte, 4)
    98  	for i := range msgs {
    99  		msgs[i] = msg
   100  	}
   101  	buf := bytes.NewBuffer(bytes.Join(msgs, []byte("\n")))
   102  
   103  	url := fmt.Sprintf("http://%s/mpub?topic=%s", httpAddr, topicName)
   104  	resp, err := http.Post(url, "application/octet-stream", buf)
   105  	test.Nil(t, err)
   106  	defer resp.Body.Close()
   107  	body, _ := io.ReadAll(resp.Body)
   108  	test.Equal(t, "OK", string(body))
   109  
   110  	time.Sleep(5 * time.Millisecond)
   111  
   112  	test.Equal(t, int64(4), topic.Depth())
   113  }
   114  
   115  func TestHTTPmpubEmpty(t *testing.T) {
   116  	opts := NewOptions()
   117  	opts.Logger = test.NewTestLogger(t)
   118  	_, httpAddr, nsqd := mustStartNSQD(opts)
   119  	defer os.RemoveAll(opts.DataPath)
   120  	defer nsqd.Exit()
   121  
   122  	topicName := "test_http_mpub_empty" + strconv.Itoa(int(time.Now().Unix()))
   123  	topic := nsqd.GetTopic(topicName)
   124  
   125  	msg := []byte("test message")
   126  	msgs := make([][]byte, 4)
   127  	for i := range msgs {
   128  		msgs[i] = msg
   129  	}
   130  	buf := bytes.NewBuffer(bytes.Join(msgs, []byte("\n")))
   131  	_, err := buf.Write([]byte("\n"))
   132  	test.Nil(t, err)
   133  
   134  	url := fmt.Sprintf("http://%s/mpub?topic=%s", httpAddr, topicName)
   135  	resp, err := http.Post(url, "application/octet-stream", buf)
   136  	test.Nil(t, err)
   137  	defer resp.Body.Close()
   138  	body, _ := io.ReadAll(resp.Body)
   139  	test.Equal(t, "OK", string(body))
   140  
   141  	time.Sleep(5 * time.Millisecond)
   142  
   143  	test.Equal(t, int64(4), topic.Depth())
   144  }
   145  
   146  func TestHTTPmpubBinary(t *testing.T) {
   147  	opts := NewOptions()
   148  	opts.Logger = test.NewTestLogger(t)
   149  	_, httpAddr, nsqd := mustStartNSQD(opts)
   150  	defer os.RemoveAll(opts.DataPath)
   151  	defer nsqd.Exit()
   152  
   153  	topicName := "test_http_mpub_bin" + strconv.Itoa(int(time.Now().Unix()))
   154  	topic := nsqd.GetTopic(topicName)
   155  
   156  	mpub := make([][]byte, 5)
   157  	for i := range mpub {
   158  		mpub[i] = make([]byte, 100)
   159  	}
   160  	cmd, _ := nsq.MultiPublish(topicName, mpub)
   161  	buf := bytes.NewBuffer(cmd.Body)
   162  
   163  	url := fmt.Sprintf("http://%s/mpub?topic=%s&binary=true", httpAddr, topicName)
   164  	resp, err := http.Post(url, "application/octet-stream", buf)
   165  	test.Nil(t, err)
   166  	defer resp.Body.Close()
   167  	body, _ := io.ReadAll(resp.Body)
   168  	test.Equal(t, "OK", string(body))
   169  
   170  	time.Sleep(5 * time.Millisecond)
   171  
   172  	test.Equal(t, int64(5), topic.Depth())
   173  }
   174  
   175  func TestHTTPmpubForNonNormalizedBinaryParam(t *testing.T) {
   176  	opts := NewOptions()
   177  	opts.Logger = test.NewTestLogger(t)
   178  	_, httpAddr, nsqd := mustStartNSQD(opts)
   179  	defer os.RemoveAll(opts.DataPath)
   180  	defer nsqd.Exit()
   181  
   182  	topicName := "test_http_mpub_bin" + strconv.Itoa(int(time.Now().Unix()))
   183  	topic := nsqd.GetTopic(topicName)
   184  
   185  	mpub := make([][]byte, 5)
   186  	for i := range mpub {
   187  		mpub[i] = make([]byte, 100)
   188  	}
   189  	cmd, _ := nsq.MultiPublish(topicName, mpub)
   190  	buf := bytes.NewBuffer(cmd.Body)
   191  
   192  	url := fmt.Sprintf("http://%s/mpub?topic=%s&binary=non_normalized_binary_param", httpAddr, topicName)
   193  	resp, err := http.Post(url, "application/octet-stream", buf)
   194  	test.Nil(t, err)
   195  	defer resp.Body.Close()
   196  	body, _ := io.ReadAll(resp.Body)
   197  	test.Equal(t, "OK", string(body))
   198  
   199  	time.Sleep(5 * time.Millisecond)
   200  
   201  	test.Equal(t, int64(5), topic.Depth())
   202  }
   203  
   204  func TestHTTPpubDefer(t *testing.T) {
   205  	opts := NewOptions()
   206  	opts.Logger = test.NewTestLogger(t)
   207  	_, httpAddr, nsqd := mustStartNSQD(opts)
   208  	defer os.RemoveAll(opts.DataPath)
   209  	defer nsqd.Exit()
   210  
   211  	topicName := "test_http_pub_defer" + strconv.Itoa(int(time.Now().Unix()))
   212  	topic := nsqd.GetTopic(topicName)
   213  	ch := topic.GetChannel("ch")
   214  
   215  	buf := bytes.NewBuffer([]byte("test message"))
   216  	url := fmt.Sprintf("http://%s/pub?topic=%s&defer=%d", httpAddr, topicName, 1000)
   217  	resp, err := http.Post(url, "application/octet-stream", buf)
   218  	test.Nil(t, err)
   219  	defer resp.Body.Close()
   220  	body, _ := io.ReadAll(resp.Body)
   221  	test.Equal(t, "OK", string(body))
   222  
   223  	time.Sleep(5 * time.Millisecond)
   224  
   225  	ch.deferredMutex.Lock()
   226  	numDef := len(ch.deferredMessages)
   227  	ch.deferredMutex.Unlock()
   228  	test.Equal(t, 1, numDef)
   229  }
   230  
   231  func TestHTTPSRequire(t *testing.T) {
   232  	opts := NewOptions()
   233  	opts.Logger = test.NewTestLogger(t)
   234  	opts.LogLevel = LOG_DEBUG
   235  	opts.TLSCert = "./test/certs/server.pem"
   236  	opts.TLSKey = "./test/certs/server.key"
   237  	opts.TLSClientAuthPolicy = "require"
   238  	_, httpAddr, nsqd := mustStartNSQD(opts)
   239  	defer os.RemoveAll(opts.DataPath)
   240  	defer nsqd.Exit()
   241  
   242  	topicName := "test_http_pub_req" + strconv.Itoa(int(time.Now().Unix()))
   243  	topic := nsqd.GetTopic(topicName)
   244  
   245  	buf := bytes.NewBuffer([]byte("test message"))
   246  	url := fmt.Sprintf("http://%s/pub?topic=%s", httpAddr, topicName)
   247  	resp, _ := http.Post(url, "application/octet-stream", buf)
   248  	test.Equal(t, 403, resp.StatusCode)
   249  
   250  	httpsAddr := nsqd.httpsListener.Addr().(*net.TCPAddr)
   251  	cert, err := tls.LoadX509KeyPair("./test/certs/cert.pem", "./test/certs/key.pem")
   252  	test.Nil(t, err)
   253  	tlsConfig := &tls.Config{
   254  		Certificates:       []tls.Certificate{cert},
   255  		InsecureSkipVerify: true,
   256  		MinVersion:         0,
   257  	}
   258  	transport := &http.Transport{
   259  		TLSClientConfig: tlsConfig,
   260  	}
   261  	client := &http.Client{Transport: transport}
   262  
   263  	buf = bytes.NewBuffer([]byte("test message"))
   264  	url = fmt.Sprintf("https://%s/pub?topic=%s", httpsAddr, topicName)
   265  	resp, err = client.Post(url, "application/octet-stream", buf)
   266  	test.Nil(t, err)
   267  	defer resp.Body.Close()
   268  	body, _ := io.ReadAll(resp.Body)
   269  	test.Equal(t, "OK", string(body))
   270  
   271  	time.Sleep(5 * time.Millisecond)
   272  
   273  	test.Equal(t, int64(1), topic.Depth())
   274  }
   275  
   276  func TestHTTPSRequireVerify(t *testing.T) {
   277  	opts := NewOptions()
   278  	opts.Logger = test.NewTestLogger(t)
   279  	opts.LogLevel = LOG_DEBUG
   280  	opts.TLSCert = "./test/certs/server.pem"
   281  	opts.TLSKey = "./test/certs/server.key"
   282  	opts.TLSRootCAFile = "./test/certs/ca.pem"
   283  	opts.TLSClientAuthPolicy = "require-verify"
   284  	_, httpAddr, nsqd := mustStartNSQD(opts)
   285  	defer os.RemoveAll(opts.DataPath)
   286  	defer nsqd.Exit()
   287  
   288  	httpsAddr := nsqd.httpsListener.Addr().(*net.TCPAddr)
   289  	topicName := "test_http_pub_req_verf" + strconv.Itoa(int(time.Now().Unix()))
   290  	topic := nsqd.GetTopic(topicName)
   291  
   292  	// no cert
   293  	buf := bytes.NewBuffer([]byte("test message"))
   294  	url := fmt.Sprintf("http://%s/pub?topic=%s", httpAddr, topicName)
   295  	resp, _ := http.Post(url, "application/octet-stream", buf)
   296  	test.Equal(t, 403, resp.StatusCode)
   297  
   298  	// unsigned cert
   299  	cert, err := tls.LoadX509KeyPair("./test/certs/cert.pem", "./test/certs/key.pem")
   300  	test.Nil(t, err)
   301  	tlsConfig := &tls.Config{
   302  		Certificates:       []tls.Certificate{cert},
   303  		InsecureSkipVerify: true,
   304  	}
   305  	transport := &http.Transport{
   306  		TLSClientConfig: tlsConfig,
   307  	}
   308  	client := &http.Client{Transport: transport}
   309  
   310  	buf = bytes.NewBuffer([]byte("test message"))
   311  	url = fmt.Sprintf("https://%s/pub?topic=%s", httpsAddr, topicName)
   312  	_, err = client.Post(url, "application/octet-stream", buf)
   313  	test.NotNil(t, err)
   314  
   315  	// signed cert
   316  	cert, err = tls.LoadX509KeyPair("./test/certs/client.pem", "./test/certs/client.key")
   317  	test.Nil(t, err)
   318  	tlsConfig = &tls.Config{
   319  		Certificates:       []tls.Certificate{cert},
   320  		InsecureSkipVerify: true,
   321  	}
   322  	transport = &http.Transport{
   323  		TLSClientConfig: tlsConfig,
   324  	}
   325  	client = &http.Client{Transport: transport}
   326  
   327  	buf = bytes.NewBuffer([]byte("test message"))
   328  	url = fmt.Sprintf("https://%s/pub?topic=%s", httpsAddr, topicName)
   329  	resp, err = client.Post(url, "application/octet-stream", buf)
   330  	test.Nil(t, err)
   331  	defer resp.Body.Close()
   332  	body, _ := io.ReadAll(resp.Body)
   333  	test.Equal(t, "OK", string(body))
   334  
   335  	time.Sleep(5 * time.Millisecond)
   336  
   337  	test.Equal(t, int64(1), topic.Depth())
   338  }
   339  
   340  func TestTLSRequireVerifyExceptHTTP(t *testing.T) {
   341  	opts := NewOptions()
   342  	opts.Logger = test.NewTestLogger(t)
   343  	opts.LogLevel = LOG_DEBUG
   344  	opts.TLSCert = "./test/certs/server.pem"
   345  	opts.TLSKey = "./test/certs/server.key"
   346  	opts.TLSRootCAFile = "./test/certs/ca.pem"
   347  	opts.TLSClientAuthPolicy = "require-verify"
   348  	opts.TLSRequired = TLSRequiredExceptHTTP
   349  	_, httpAddr, nsqd := mustStartNSQD(opts)
   350  	defer os.RemoveAll(opts.DataPath)
   351  	defer nsqd.Exit()
   352  
   353  	topicName := "test_http_req_verf_except_http" + strconv.Itoa(int(time.Now().Unix()))
   354  	topic := nsqd.GetTopic(topicName)
   355  
   356  	// no cert
   357  	buf := bytes.NewBuffer([]byte("test message"))
   358  	url := fmt.Sprintf("http://%s/pub?topic=%s", httpAddr, topicName)
   359  	resp, err := http.Post(url, "application/octet-stream", buf)
   360  	test.Nil(t, err)
   361  	defer resp.Body.Close()
   362  	body, _ := io.ReadAll(resp.Body)
   363  	test.Equal(t, "OK", string(body))
   364  
   365  	time.Sleep(5 * time.Millisecond)
   366  
   367  	test.Equal(t, int64(1), topic.Depth())
   368  }
   369  
   370  func TestHTTPV1TopicChannel(t *testing.T) {
   371  	opts := NewOptions()
   372  	opts.Logger = test.NewTestLogger(t)
   373  	_, httpAddr, nsqd := mustStartNSQD(opts)
   374  	defer os.RemoveAll(opts.DataPath)
   375  	defer nsqd.Exit()
   376  
   377  	topicName := "test_http_topic_channel2" + strconv.Itoa(int(time.Now().Unix()))
   378  	channelName := "ch2"
   379  
   380  	url := fmt.Sprintf("http://%s/topic/create?topic=%s", httpAddr, topicName)
   381  	resp, err := http.Post(url, "application/json", nil)
   382  	test.Nil(t, err)
   383  	test.Equal(t, 200, resp.StatusCode)
   384  	body, _ := io.ReadAll(resp.Body)
   385  	resp.Body.Close()
   386  	test.Equal(t, "", string(body))
   387  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   388  
   389  	url = fmt.Sprintf("http://%s/channel/create?topic=%s&channel=%s", httpAddr, topicName, channelName)
   390  	resp, err = http.Post(url, "application/json", nil)
   391  	test.Nil(t, err)
   392  	test.Equal(t, 200, resp.StatusCode)
   393  	body, _ = io.ReadAll(resp.Body)
   394  	resp.Body.Close()
   395  	test.Equal(t, "", string(body))
   396  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   397  
   398  	topic, err := nsqd.GetExistingTopic(topicName)
   399  	test.Nil(t, err)
   400  	test.NotNil(t, topic)
   401  
   402  	channel, err := topic.GetExistingChannel(channelName)
   403  	test.Nil(t, err)
   404  	test.NotNil(t, channel)
   405  
   406  	em := ErrMessage{}
   407  
   408  	url = fmt.Sprintf("http://%s/topic/pause", httpAddr)
   409  	resp, err = http.Post(url, "application/json", nil)
   410  	test.Nil(t, err)
   411  	test.Equal(t, 400, resp.StatusCode)
   412  	test.Equal(t, "Bad Request", http.StatusText(resp.StatusCode))
   413  	body, _ = io.ReadAll(resp.Body)
   414  	resp.Body.Close()
   415  
   416  	t.Logf("%s", body)
   417  	err = json.Unmarshal(body, &em)
   418  	test.Nil(t, err)
   419  	test.Equal(t, "MISSING_ARG_TOPIC", em.Message)
   420  
   421  	url = fmt.Sprintf("http://%s/topic/pause?topic=%s", httpAddr, topicName+"abc")
   422  	resp, err = http.Post(url, "application/json", nil)
   423  	test.Nil(t, err)
   424  	test.Equal(t, 404, resp.StatusCode)
   425  	test.Equal(t, "Not Found", http.StatusText(resp.StatusCode))
   426  	body, _ = io.ReadAll(resp.Body)
   427  	resp.Body.Close()
   428  
   429  	t.Logf("%s", body)
   430  	err = json.Unmarshal(body, &em)
   431  	test.Nil(t, err)
   432  	test.Equal(t, "TOPIC_NOT_FOUND", em.Message)
   433  
   434  	url = fmt.Sprintf("http://%s/topic/pause?topic=%s", httpAddr, topicName)
   435  	resp, err = http.Post(url, "application/json", nil)
   436  	test.Nil(t, err)
   437  	test.Equal(t, 200, resp.StatusCode)
   438  	body, _ = io.ReadAll(resp.Body)
   439  	resp.Body.Close()
   440  	test.Equal(t, "", string(body))
   441  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   442  
   443  	test.Equal(t, true, topic.IsPaused())
   444  
   445  	url = fmt.Sprintf("http://%s/topic/unpause?topic=%s", httpAddr, topicName)
   446  	resp, err = http.Post(url, "application/json", nil)
   447  	test.Nil(t, err)
   448  	test.Equal(t, 200, resp.StatusCode)
   449  	body, _ = io.ReadAll(resp.Body)
   450  	resp.Body.Close()
   451  	test.Equal(t, "", string(body))
   452  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   453  
   454  	test.Equal(t, false, topic.IsPaused())
   455  
   456  	url = fmt.Sprintf("http://%s/channel/pause?topic=%s&channel=%s", httpAddr, topicName, channelName)
   457  	resp, err = http.Post(url, "application/json", nil)
   458  	test.Nil(t, err)
   459  	test.Equal(t, 200, resp.StatusCode)
   460  	body, _ = io.ReadAll(resp.Body)
   461  	resp.Body.Close()
   462  	test.Equal(t, "", string(body))
   463  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   464  
   465  	test.Equal(t, true, channel.IsPaused())
   466  
   467  	url = fmt.Sprintf("http://%s/channel/unpause?topic=%s&channel=%s", httpAddr, topicName, channelName)
   468  	resp, err = http.Post(url, "application/json", nil)
   469  	test.Nil(t, err)
   470  	test.Equal(t, 200, resp.StatusCode)
   471  	body, _ = io.ReadAll(resp.Body)
   472  	resp.Body.Close()
   473  	test.Equal(t, "", string(body))
   474  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   475  
   476  	test.Equal(t, false, channel.IsPaused())
   477  
   478  	url = fmt.Sprintf("http://%s/channel/delete?topic=%s&channel=%s", httpAddr, topicName, channelName)
   479  	resp, err = http.Post(url, "application/json", nil)
   480  	test.Nil(t, err)
   481  	test.Equal(t, 200, resp.StatusCode)
   482  	body, _ = io.ReadAll(resp.Body)
   483  	resp.Body.Close()
   484  	test.Equal(t, "", string(body))
   485  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   486  
   487  	_, err = topic.GetExistingChannel(channelName)
   488  	test.NotNil(t, err)
   489  
   490  	url = fmt.Sprintf("http://%s/topic/delete?topic=%s", httpAddr, topicName)
   491  	resp, err = http.Post(url, "application/json", nil)
   492  	test.Nil(t, err)
   493  	test.Equal(t, 200, resp.StatusCode)
   494  	body, _ = io.ReadAll(resp.Body)
   495  	resp.Body.Close()
   496  	test.Equal(t, "", string(body))
   497  	test.Equal(t, "nsq; version=1.0", resp.Header.Get("X-NSQ-Content-Type"))
   498  
   499  	_, err = nsqd.GetExistingTopic(topicName)
   500  	test.NotNil(t, err)
   501  }
   502  
   503  func TestHTTPClientStats(t *testing.T) {
   504  	topicName := "test_http_client_stats" + strconv.Itoa(int(time.Now().Unix()))
   505  
   506  	opts := NewOptions()
   507  	opts.Logger = test.NewTestLogger(t)
   508  	tcpAddr, httpAddr, nsqd := mustStartNSQD(opts)
   509  	defer os.RemoveAll(opts.DataPath)
   510  	defer nsqd.Exit()
   511  
   512  	conn, err := mustConnectNSQD(tcpAddr)
   513  	test.Nil(t, err)
   514  	defer conn.Close()
   515  
   516  	identify(t, conn, nil, frameTypeResponse)
   517  	sub(t, conn, topicName, "ch")
   518  
   519  	var d struct {
   520  		Topics []struct {
   521  			Channels []struct {
   522  				ClientCount int `json:"client_count"`
   523  				Clients     []struct {
   524  				} `json:"clients"`
   525  			} `json:"channels"`
   526  		} `json:"topics"`
   527  		Memory *struct{} `json:"memory,omitempty"`
   528  	}
   529  
   530  	endpoint := fmt.Sprintf("http://%s/stats?format=json", httpAddr)
   531  	err = http_api.NewClient(nil, ConnectTimeout, RequestTimeout).GETV1(endpoint, &d)
   532  	test.Nil(t, err)
   533  
   534  	test.Equal(t, 1, len(d.Topics[0].Channels[0].Clients))
   535  	test.Equal(t, 1, d.Topics[0].Channels[0].ClientCount)
   536  	test.NotNil(t, d.Memory)
   537  
   538  	endpoint = fmt.Sprintf("http://%s/stats?format=json&include_clients=true", httpAddr)
   539  	err = http_api.NewClient(nil, ConnectTimeout, RequestTimeout).GETV1(endpoint, &d)
   540  	test.Nil(t, err)
   541  
   542  	test.Equal(t, 1, len(d.Topics[0].Channels[0].Clients))
   543  	test.Equal(t, 1, d.Topics[0].Channels[0].ClientCount)
   544  
   545  	endpoint = fmt.Sprintf("http://%s/stats?format=json&include_clients=false", httpAddr)
   546  	err = http_api.NewClient(nil, ConnectTimeout, RequestTimeout).GETV1(endpoint, &d)
   547  	test.Nil(t, err)
   548  
   549  	test.Equal(t, 0, len(d.Topics[0].Channels[0].Clients))
   550  	test.Equal(t, 1, d.Topics[0].Channels[0].ClientCount)
   551  
   552  	endpoint = fmt.Sprintf("http://%s/stats?format=json&include_mem=true", httpAddr)
   553  	err = http_api.NewClient(nil, ConnectTimeout, RequestTimeout).GETV1(endpoint, &d)
   554  	test.Nil(t, err)
   555  
   556  	test.NotNil(t, d.Memory)
   557  
   558  	d.Memory = nil
   559  	endpoint = fmt.Sprintf("http://%s/stats?format=json&include_mem=false", httpAddr)
   560  	err = http_api.NewClient(nil, ConnectTimeout, RequestTimeout).GETV1(endpoint, &d)
   561  	test.Nil(t, err)
   562  
   563  	test.Nil(t, d.Memory)
   564  }
   565  
   566  func TestHTTPgetStatusJSON(t *testing.T) {
   567  	testTime := time.Now()
   568  	opts := NewOptions()
   569  	opts.Logger = test.NewTestLogger(t)
   570  	_, httpAddr, nsqd := mustStartNSQD(opts)
   571  	defer os.RemoveAll(opts.DataPath)
   572  	defer nsqd.Exit()
   573  
   574  	nsqd.startTime = testTime
   575  	expectedJSON := fmt.Sprintf(`{"version":"%v","health":"OK","start_time":%v,"topics":[],"memory":{`, version.Binary, testTime.Unix())
   576  
   577  	url := fmt.Sprintf("http://%s/stats?format=json", httpAddr)
   578  	resp, err := http.Get(url)
   579  	test.Nil(t, err)
   580  	defer resp.Body.Close()
   581  	body, _ := io.ReadAll(resp.Body)
   582  	test.Equal(t, 200, resp.StatusCode)
   583  	test.Equal(t, true, strings.HasPrefix(string(body), expectedJSON))
   584  }
   585  
   586  func TestHTTPgetStatusText(t *testing.T) {
   587  	testTime := time.Now()
   588  	opts := NewOptions()
   589  	opts.Logger = test.NewTestLogger(t)
   590  	_, httpAddr, nsqd := mustStartNSQD(opts)
   591  	defer os.RemoveAll(opts.DataPath)
   592  	defer nsqd.Exit()
   593  
   594  	nsqd.startTime = testTime
   595  
   596  	url := fmt.Sprintf("http://%s/stats?format=text", httpAddr)
   597  	resp, err := http.Get(url)
   598  	test.Nil(t, err)
   599  	defer resp.Body.Close()
   600  	body, _ := io.ReadAll(resp.Body)
   601  	test.Equal(t, 200, resp.StatusCode)
   602  	test.NotNil(t, body)
   603  }
   604  
   605  func TestHTTPconfig(t *testing.T) {
   606  	lopts := nsqlookupd.NewOptions()
   607  	lopts.Logger = test.NewTestLogger(t)
   608  
   609  	lopts1 := *lopts
   610  	_, _, lookupd1 := mustStartNSQLookupd(&lopts1)
   611  	defer lookupd1.Exit()
   612  	lopts2 := *lopts
   613  	_, _, lookupd2 := mustStartNSQLookupd(&lopts2)
   614  	defer lookupd2.Exit()
   615  
   616  	opts := NewOptions()
   617  	opts.Logger = test.NewTestLogger(t)
   618  	_, httpAddr, nsqd := mustStartNSQD(opts)
   619  	defer os.RemoveAll(opts.DataPath)
   620  	defer nsqd.Exit()
   621  
   622  	url := fmt.Sprintf("http://%s/config/nsqlookupd_tcp_addresses", httpAddr)
   623  	resp, err := http.Get(url)
   624  	test.Nil(t, err)
   625  	defer resp.Body.Close()
   626  	body, _ := io.ReadAll(resp.Body)
   627  	test.Equal(t, 200, resp.StatusCode)
   628  	test.Equal(t, "[]", string(body))
   629  
   630  	client := http.Client{}
   631  	addrs := fmt.Sprintf(`["%s","%s"]`, lookupd1.RealTCPAddr().String(), lookupd2.RealTCPAddr().String())
   632  	url = fmt.Sprintf("http://%s/config/nsqlookupd_tcp_addresses", httpAddr)
   633  	req, err := http.NewRequest("PUT", url, bytes.NewBuffer([]byte(addrs)))
   634  	test.Nil(t, err)
   635  	resp, err = client.Do(req)
   636  	test.Nil(t, err)
   637  	defer resp.Body.Close()
   638  	body, _ = io.ReadAll(resp.Body)
   639  	test.Equal(t, 200, resp.StatusCode)
   640  	test.Equal(t, addrs, string(body))
   641  
   642  	url = fmt.Sprintf("http://%s/config/log_level", httpAddr)
   643  	req, err = http.NewRequest("PUT", url, bytes.NewBuffer([]byte(`fatal`)))
   644  	test.Nil(t, err)
   645  	resp, err = client.Do(req)
   646  	test.Nil(t, err)
   647  	defer resp.Body.Close()
   648  	_, _ = io.ReadAll(resp.Body)
   649  	test.Equal(t, 200, resp.StatusCode)
   650  	test.Equal(t, LOG_FATAL, nsqd.getOpts().LogLevel)
   651  
   652  	url = fmt.Sprintf("http://%s/config/log_level", httpAddr)
   653  	req, err = http.NewRequest("PUT", url, bytes.NewBuffer([]byte(`bad`)))
   654  	test.Nil(t, err)
   655  	resp, err = client.Do(req)
   656  	test.Nil(t, err)
   657  	defer resp.Body.Close()
   658  	_, _ = io.ReadAll(resp.Body)
   659  	test.Equal(t, 400, resp.StatusCode)
   660  }
   661  
   662  func TestHTTPerrors(t *testing.T) {
   663  	opts := NewOptions()
   664  	opts.Logger = test.NewTestLogger(t)
   665  	_, httpAddr, nsqd := mustStartNSQD(opts)
   666  	defer os.RemoveAll(opts.DataPath)
   667  	defer nsqd.Exit()
   668  
   669  	url := fmt.Sprintf("http://%s/stats", httpAddr)
   670  	resp, err := http.Post(url, "text/plain", nil)
   671  	test.Nil(t, err)
   672  	defer resp.Body.Close()
   673  	body, _ := io.ReadAll(resp.Body)
   674  	test.Equal(t, 405, resp.StatusCode)
   675  	test.Equal(t, `{"message":"METHOD_NOT_ALLOWED"}`, string(body))
   676  
   677  	url = fmt.Sprintf("http://%s/not_found", httpAddr)
   678  	resp, err = http.Get(url)
   679  	test.Nil(t, err)
   680  	defer resp.Body.Close()
   681  	body, _ = io.ReadAll(resp.Body)
   682  	test.Equal(t, 404, resp.StatusCode)
   683  	test.Equal(t, `{"message":"NOT_FOUND"}`, string(body))
   684  }
   685  
   686  func TestDeleteTopic(t *testing.T) {
   687  	opts := NewOptions()
   688  	opts.Logger = test.NewTestLogger(t)
   689  	_, httpAddr, nsqd := mustStartNSQD(opts)
   690  	defer os.RemoveAll(opts.DataPath)
   691  	defer nsqd.Exit()
   692  
   693  	em := ErrMessage{}
   694  
   695  	url := fmt.Sprintf("http://%s/topic/delete", httpAddr)
   696  	resp, err := http.Post(url, "application/json", nil)
   697  	test.Nil(t, err)
   698  	test.Equal(t, 400, resp.StatusCode)
   699  	test.Equal(t, "Bad Request", http.StatusText(resp.StatusCode))
   700  	body, _ := io.ReadAll(resp.Body)
   701  	resp.Body.Close()
   702  
   703  	t.Logf("%s", body)
   704  	err = json.Unmarshal(body, &em)
   705  	test.Nil(t, err)
   706  	test.Equal(t, "MISSING_ARG_TOPIC", em.Message)
   707  
   708  	topicName := "test_http_delete_topic" + strconv.Itoa(int(time.Now().Unix()))
   709  
   710  	url = fmt.Sprintf("http://%s/topic/delete?topic=%s", httpAddr, topicName)
   711  	resp, err = http.Post(url, "application/json", nil)
   712  	test.Nil(t, err)
   713  	test.Equal(t, 404, resp.StatusCode)
   714  	test.Equal(t, "Not Found", http.StatusText(resp.StatusCode))
   715  	body, _ = io.ReadAll(resp.Body)
   716  	resp.Body.Close()
   717  
   718  	t.Logf("%s", body)
   719  	err = json.Unmarshal(body, &em)
   720  	test.Nil(t, err)
   721  	test.Equal(t, "TOPIC_NOT_FOUND", em.Message)
   722  
   723  	nsqd.GetTopic(topicName)
   724  
   725  	resp, err = http.Post(url, "application/json", nil)
   726  	test.Nil(t, err)
   727  	test.Equal(t, 200, resp.StatusCode)
   728  	body, _ = io.ReadAll(resp.Body)
   729  	resp.Body.Close()
   730  
   731  	t.Logf("%s", body)
   732  	test.Equal(t, []byte(""), body)
   733  }
   734  
   735  func TestEmptyTopic(t *testing.T) {
   736  	opts := NewOptions()
   737  	opts.Logger = test.NewTestLogger(t)
   738  	_, httpAddr, nsqd := mustStartNSQD(opts)
   739  	defer os.RemoveAll(opts.DataPath)
   740  	defer nsqd.Exit()
   741  
   742  	em := ErrMessage{}
   743  
   744  	url := fmt.Sprintf("http://%s/topic/empty", httpAddr)
   745  	resp, err := http.Post(url, "application/json", nil)
   746  	test.Nil(t, err)
   747  	test.Equal(t, 400, resp.StatusCode)
   748  	test.Equal(t, "Bad Request", http.StatusText(resp.StatusCode))
   749  	body, _ := io.ReadAll(resp.Body)
   750  	resp.Body.Close()
   751  
   752  	t.Logf("%s", body)
   753  	err = json.Unmarshal(body, &em)
   754  	test.Nil(t, err)
   755  	test.Equal(t, "MISSING_ARG_TOPIC", em.Message)
   756  
   757  	topicName := "test_http_empty_topic" + strconv.Itoa(int(time.Now().Unix()))
   758  
   759  	url = fmt.Sprintf("http://%s/topic/empty?topic=%s", httpAddr, topicName+"$")
   760  	resp, err = http.Post(url, "application/json", nil)
   761  	test.Nil(t, err)
   762  	test.Equal(t, 400, resp.StatusCode)
   763  	test.Equal(t, "Bad Request", http.StatusText(resp.StatusCode))
   764  	body, _ = io.ReadAll(resp.Body)
   765  	resp.Body.Close()
   766  
   767  	t.Logf("%s", body)
   768  	err = json.Unmarshal(body, &em)
   769  	test.Nil(t, err)
   770  	test.Equal(t, "INVALID_TOPIC", em.Message)
   771  
   772  	url = fmt.Sprintf("http://%s/topic/empty?topic=%s", httpAddr, topicName)
   773  	resp, err = http.Post(url, "application/json", nil)
   774  	test.Nil(t, err)
   775  	test.Equal(t, 404, resp.StatusCode)
   776  	test.Equal(t, "Not Found", http.StatusText(resp.StatusCode))
   777  	body, _ = io.ReadAll(resp.Body)
   778  	resp.Body.Close()
   779  
   780  	t.Logf("%s", body)
   781  	err = json.Unmarshal(body, &em)
   782  	test.Nil(t, err)
   783  	test.Equal(t, "TOPIC_NOT_FOUND", em.Message)
   784  
   785  	nsqd.GetTopic(topicName)
   786  
   787  	resp, err = http.Post(url, "application/json", nil)
   788  	test.Nil(t, err)
   789  	test.Equal(t, 200, resp.StatusCode)
   790  	body, _ = io.ReadAll(resp.Body)
   791  	resp.Body.Close()
   792  
   793  	t.Logf("%s", body)
   794  	test.Equal(t, []byte(""), body)
   795  }
   796  
   797  func TestEmptyChannel(t *testing.T) {
   798  	opts := NewOptions()
   799  	opts.Logger = test.NewTestLogger(t)
   800  	_, httpAddr, nsqd := mustStartNSQD(opts)
   801  	defer os.RemoveAll(opts.DataPath)
   802  	defer nsqd.Exit()
   803  
   804  	em := ErrMessage{}
   805  
   806  	url := fmt.Sprintf("http://%s/channel/empty", httpAddr)
   807  	resp, err := http.Post(url, "application/json", nil)
   808  	test.Nil(t, err)
   809  	test.Equal(t, 400, resp.StatusCode)
   810  	test.Equal(t, "Bad Request", http.StatusText(resp.StatusCode))
   811  	body, _ := io.ReadAll(resp.Body)
   812  	resp.Body.Close()
   813  
   814  	t.Logf("%s", body)
   815  	err = json.Unmarshal(body, &em)
   816  	test.Nil(t, err)
   817  	test.Equal(t, "MISSING_ARG_TOPIC", em.Message)
   818  
   819  	topicName := "test_http_empty_channel" + strconv.Itoa(int(time.Now().Unix()))
   820  
   821  	url = fmt.Sprintf("http://%s/channel/empty?topic=%s", httpAddr, topicName)
   822  	resp, err = http.Post(url, "application/json", nil)
   823  	test.Nil(t, err)
   824  	test.Equal(t, 400, resp.StatusCode)
   825  	test.Equal(t, "Bad Request", http.StatusText(resp.StatusCode))
   826  	body, _ = io.ReadAll(resp.Body)
   827  	resp.Body.Close()
   828  
   829  	t.Logf("%s", body)
   830  	err = json.Unmarshal(body, &em)
   831  	test.Nil(t, err)
   832  	test.Equal(t, "MISSING_ARG_CHANNEL", em.Message)
   833  
   834  	channelName := "ch"
   835  
   836  	url = fmt.Sprintf("http://%s/channel/empty?topic=%s&channel=%s", httpAddr, topicName, channelName)
   837  	resp, err = http.Post(url, "application/json", nil)
   838  	test.Nil(t, err)
   839  	test.Equal(t, 404, resp.StatusCode)
   840  	test.Equal(t, "Not Found", http.StatusText(resp.StatusCode))
   841  	body, _ = io.ReadAll(resp.Body)
   842  	resp.Body.Close()
   843  
   844  	t.Logf("%s", body)
   845  	err = json.Unmarshal(body, &em)
   846  	test.Nil(t, err)
   847  	test.Equal(t, "TOPIC_NOT_FOUND", em.Message)
   848  
   849  	topic := nsqd.GetTopic(topicName)
   850  
   851  	url = fmt.Sprintf("http://%s/channel/empty?topic=%s&channel=%s", httpAddr, topicName, channelName)
   852  	resp, err = http.Post(url, "application/json", nil)
   853  	test.Nil(t, err)
   854  	test.Equal(t, 404, resp.StatusCode)
   855  	test.Equal(t, "Not Found", http.StatusText(resp.StatusCode))
   856  	body, _ = io.ReadAll(resp.Body)
   857  	resp.Body.Close()
   858  
   859  	t.Logf("%s", body)
   860  	err = json.Unmarshal(body, &em)
   861  	test.Nil(t, err)
   862  	test.Equal(t, "CHANNEL_NOT_FOUND", em.Message)
   863  
   864  	topic.GetChannel(channelName)
   865  
   866  	resp, err = http.Post(url, "application/json", nil)
   867  	test.Nil(t, err)
   868  	test.Equal(t, 200, resp.StatusCode)
   869  	body, _ = io.ReadAll(resp.Body)
   870  	resp.Body.Close()
   871  
   872  	t.Logf("%s", body)
   873  	test.Equal(t, []byte(""), body)
   874  }
   875  
   876  func TestInfo(t *testing.T) {
   877  	opts := NewOptions()
   878  	opts.Logger = test.NewTestLogger(t)
   879  	_, httpAddr, nsqd := mustStartNSQD(opts)
   880  	defer os.RemoveAll(opts.DataPath)
   881  	defer nsqd.Exit()
   882  
   883  	info := InfoDoc{}
   884  
   885  	url := fmt.Sprintf("http://%s/info", httpAddr)
   886  	resp, err := http.Get(url)
   887  	test.Nil(t, err)
   888  	test.Equal(t, 200, resp.StatusCode)
   889  	body, _ := io.ReadAll(resp.Body)
   890  	resp.Body.Close()
   891  
   892  	t.Logf("%s", body)
   893  	err = json.Unmarshal(body, &info)
   894  	test.Nil(t, err)
   895  	test.Equal(t, version.Binary, info.Version)
   896  }
   897  
   898  func BenchmarkHTTPpub(b *testing.B) {
   899  	var wg sync.WaitGroup
   900  	b.StopTimer()
   901  	opts := NewOptions()
   902  	opts.Logger = test.NewTestLogger(b)
   903  	opts.MemQueueSize = int64(b.N)
   904  	_, httpAddr, nsqd := mustStartNSQD(opts)
   905  	defer os.RemoveAll(opts.DataPath)
   906  	msg := make([]byte, 256)
   907  	topicName := "bench_http_pub" + strconv.Itoa(int(time.Now().Unix()))
   908  	url := fmt.Sprintf("http://%s/pub?topic=%s", httpAddr, topicName)
   909  	client := &http.Client{}
   910  	b.SetBytes(int64(len(msg)))
   911  	b.StartTimer()
   912  
   913  	for j := 0; j < runtime.GOMAXPROCS(0); j++ {
   914  		wg.Add(1)
   915  		go func() {
   916  			num := b.N / runtime.GOMAXPROCS(0)
   917  			for i := 0; i < num; i++ {
   918  				buf := bytes.NewBuffer(msg)
   919  				req, _ := http.NewRequest("POST", url, buf)
   920  				resp, err := client.Do(req)
   921  				if err != nil {
   922  					panic(err.Error())
   923  				}
   924  				body, _ := io.ReadAll(resp.Body)
   925  				if !bytes.Equal(body, []byte("OK")) {
   926  					panic("bad response")
   927  				}
   928  				resp.Body.Close()
   929  			}
   930  			wg.Done()
   931  		}()
   932  	}
   933  
   934  	wg.Wait()
   935  
   936  	b.StopTimer()
   937  	nsqd.Exit()
   938  }