requesthandler_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. /*
  2. * DudelDu
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the MIT
  7. * License, If a copy of the MIT License was not distributed with this
  8. * file, You can obtain one at https://opensource.org/licenses/MIT.
  9. */
  10. package dudeldu
  11. import (
  12. "bytes"
  13. "errors"
  14. "fmt"
  15. "log"
  16. "net"
  17. "strings"
  18. "sync"
  19. "testing"
  20. "devt.de/krotik/common/testutil"
  21. )
  22. const testRequest = `
  23. GET /mylist HTTP/1.1
  24. Host: localhost:9091
  25. User-Agent: VLC/2.2.1 LibVLC/2.2.1
  26. Range: bytes=0-
  27. Connection: close
  28. Icy-MetaData: 1` +
  29. "\r\n\r\n"
  30. const testRequest2 = `
  31. GET /mylist2 HTTP/1.1
  32. Host: localhost:9091
  33. User-Agent: VLC/2.2.1 LibVLC/2.2.1
  34. Range: bytes=656-
  35. Connection: close
  36. Icy-MetaData: 1` +
  37. "\r\n\r\n"
  38. const testRequest3 = `
  39. GET /bach/cello_suite1 HTTP/1.1
  40. Host: localhost:9091
  41. User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:48.0) Gecko/20100101 Firefox/99.0
  42. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
  43. Accept-Language: en-US,en;q=0.5
  44. Accept-Encoding: gzip, deflate
  45. Authorization: Basic d2ViOndlYg==
  46. Connection: keep-alive
  47. Upgrade-Insecure-Requests: 1
  48. Cache-Control: max-age=0
  49. `
  50. const testRequest4 = "GET /bach/cello_suite1 HTTP/1.1\r\nHost: localhost:9091\r\n" +
  51. "User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:48.0) Gecko/20100101 Firefox/48.0\r\n" +
  52. "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: en-US,en;q=0.5\r\n" +
  53. "Accept-Encoding: gzip, deflate\r\n" +
  54. "Authorization: Basic d2ViOndlYg==\r\n" +
  55. "Connection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nCache-Control: max-age=0"
  56. const testRequest5 = `
  57. GET /mylist2 HTTP/1.1
  58. Host: localhost:9091
  59. User-Agent: VLC/2.2.1 LibVLC/2.2.1
  60. Range: bytes=656-
  61. Authorization: Basic erghb4
  62. Connection: close
  63. Icy-MetaData: 1` +
  64. "\r\n\r\n"
  65. /*
  66. testNetError is am error for testing
  67. */
  68. type testNetError struct {
  69. }
  70. func (t *testNetError) Error() string {
  71. return "TestNetError"
  72. }
  73. func (t *testNetError) Timeout() bool {
  74. return false
  75. }
  76. func (t *testNetError) Temporary() bool {
  77. return false
  78. }
  79. type testPlaylistFactory struct {
  80. RetPlaylist Playlist
  81. }
  82. func (tp *testPlaylistFactory) Playlist(path string, shuffle bool) Playlist {
  83. if path == "/testpath" {
  84. return tp.RetPlaylist
  85. }
  86. return nil
  87. }
  88. var testTitle = "Test Title"
  89. /*
  90. testPlaylist is a playlist for testing
  91. */
  92. type testPlaylist struct {
  93. Frames [][]byte
  94. Errors []error
  95. fp int
  96. }
  97. func (tp *testPlaylist) Name() string {
  98. return "TestPlaylist"
  99. }
  100. func (tp *testPlaylist) ContentType() string {
  101. return "Test/Content"
  102. }
  103. func (tp *testPlaylist) Artist() string {
  104. return "Test Artist"
  105. }
  106. func (tp *testPlaylist) Title() string {
  107. return testTitle
  108. }
  109. func (tp *testPlaylist) Frame() ([]byte, error) {
  110. var err error
  111. f := tp.Frames[tp.fp]
  112. if tp.Errors != nil {
  113. err = tp.Errors[tp.fp]
  114. }
  115. tp.fp++
  116. return f, err
  117. }
  118. func (tp *testPlaylist) ReleaseFrame([]byte) {
  119. }
  120. func (tp *testPlaylist) Finished() bool {
  121. return tp.fp == len(tp.Frames)
  122. }
  123. func (tp *testPlaylist) Close() error {
  124. tp.fp = 0
  125. return nil
  126. }
  127. func TestRequestServing(t *testing.T) {
  128. DebugOutput = true
  129. var out bytes.Buffer
  130. // Collect the print output
  131. Print = func(v ...interface{}) {
  132. out.WriteString(fmt.Sprint(v...))
  133. out.WriteString("\n")
  134. }
  135. defer func() {
  136. Print = log.Print
  137. }()
  138. drh := NewDefaultRequestHandler(&testPlaylistFactory{}, false, false, "")
  139. testConn := &testutil.ErrorTestingConnection{}
  140. // Test a path not found
  141. drh.defaultServeRequest(testConn, "tester", false, 0, "")
  142. if testConn.Out.String() != "HTTP/1.1 404 Not found\r\n\r\n" {
  143. t.Error("Unexpected response:", testConn.Out.String())
  144. return
  145. }
  146. // Test straight forward case - serving a stream without meta data
  147. drh = NewDefaultRequestHandler(&testPlaylistFactory{&testPlaylist{
  148. [][]byte{[]byte("12"), nil, []byte("3")},
  149. []error{nil, nil, errors.New("TestError")},
  150. 0}}, false, false, "")
  151. testConn = &testutil.ErrorTestingConnection{}
  152. out.Reset()
  153. drh.defaultServeRequest(testConn, "/testpath", false, 0, "")
  154. if testConn.Out.String() != "ICY 200 OK\r\n"+
  155. "Content-Type: Test/Content\r\n"+
  156. "icy-name: TestPlaylist\r\n"+
  157. "\r\n"+
  158. "123" {
  159. t.Error("Unexpected response:", testConn.Out.String())
  160. return
  161. }
  162. if out.String() != "Serve request path:/testpath Metadata support:false Offset:0\n"+
  163. "Written bytes: 0\n"+
  164. "Sending: Test Title - Test Artist\n"+
  165. "Empty frame for: Test Title - Test Artist (Error: <nil>)\n"+
  166. "Error while retrieving playlist data: TestError\n"+
  167. "Serve request path:/testpath complete\n" {
  168. t.Error("Unexpected out string:", out.String())
  169. return
  170. }
  171. // Test case when sending meta data
  172. oldMetaDataInterval := MetaDataInterval
  173. MetaDataInterval = 5
  174. defer func() {
  175. MetaDataInterval = oldMetaDataInterval
  176. }()
  177. tpl := &testPlaylist{[][]byte{[]byte("123"), []byte("4567"), []byte("0123"), []byte("456789")}, nil, 0}
  178. drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
  179. testConn = &testutil.ErrorTestingConnection{}
  180. drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
  181. // Meta data is 3*16=48 bytes - text is 39 bytes, padding is 9 bytes
  182. if testConn.Out.String() != ("ICY 200 OK\r\n" +
  183. "Content-Type: Test/Content\r\n" +
  184. "icy-name: TestPlaylist\r\n" +
  185. "icy-metadata: 1\r\n" +
  186. "icy-metaint: 5\r\n" +
  187. "\r\n" +
  188. `12345` + string(0x03) + `StreamTitle='Test Title - Test Artist';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  189. `67012` + string(0x03) + `StreamTitle='Test Title - Test Artist';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  190. `34567` + string(0x03) + `StreamTitle='Test Title - Test Artist';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  191. `89`) {
  192. t.Error("Unexpected response:", testConn.Out.String())
  193. return
  194. }
  195. tpl.fp = 0
  196. drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
  197. testConn = &testutil.ErrorTestingConnection{}
  198. testConn.OutErr = 5
  199. out.Reset()
  200. drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
  201. if out.String() != "Serve request path:/testpath Metadata support:true Offset:0\n"+
  202. "Written bytes: 0\n"+
  203. "Sending: Test Title - Test Artist\n"+
  204. "Test writing error\n" {
  205. t.Error("Unexpected output:", out.String())
  206. return
  207. }
  208. oldTestTitle := testTitle
  209. testTitle = "A very long title name which should be truncated"
  210. defer func() {
  211. testTitle = oldTestTitle
  212. }()
  213. oldMaxMetaDataSize := MaxMetaDataSize
  214. MaxMetaDataSize = 40
  215. defer func() {
  216. MaxMetaDataSize = oldMaxMetaDataSize
  217. }()
  218. tpl.fp = 0
  219. drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
  220. testConn = &testutil.ErrorTestingConnection{}
  221. drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
  222. // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
  223. if testConn.Out.String() != ("ICY 200 OK\r\n" +
  224. "Content-Type: Test/Content\r\n" +
  225. "icy-name: TestPlaylist\r\n" +
  226. "icy-metadata: 1\r\n" +
  227. "icy-metaint: 5\r\n" +
  228. "\r\n" +
  229. `12345` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  230. `67012` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  231. `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  232. `89`) {
  233. t.Error("Unexpected response:", testConn.Out.String())
  234. return
  235. }
  236. // Test offsets
  237. tpl.fp = 0
  238. drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
  239. testConn = &testutil.ErrorTestingConnection{}
  240. drh.defaultServeRequest(testConn, "/testpath", true, 7, "")
  241. // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
  242. if testConn.Out.String() != ("ICY 200 OK\r\n" +
  243. "Content-Type: Test/Content\r\n" +
  244. "icy-name: TestPlaylist\r\n" +
  245. "icy-metadata: 1\r\n" +
  246. "icy-metaint: 5\r\n" +
  247. "\r\n" +
  248. `01234` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  249. `56789`) {
  250. t.Error("Unexpected response:", testConn.Out.String())
  251. return
  252. }
  253. tpl.fp = 0
  254. drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
  255. testConn = &testutil.ErrorTestingConnection{}
  256. drh.defaultServeRequest(testConn, "/testpath", true, 2, "")
  257. // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
  258. if testConn.Out.String() != ("ICY 200 OK\r\n" +
  259. "Content-Type: Test/Content\r\n" +
  260. "icy-name: TestPlaylist\r\n" +
  261. "icy-metadata: 1\r\n" +
  262. "icy-metaint: 5\r\n" +
  263. "\r\n" +
  264. `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  265. `01234` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  266. `56789`) {
  267. t.Error("Unexpected response:", testConn.Out.String())
  268. return
  269. }
  270. // Test offset and loops
  271. tpl.fp = 0
  272. drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, true, false, "")
  273. testConn = &testutil.ErrorTestingConnection{}
  274. drh.LoopTimes = 3
  275. drh.defaultServeRequest(testConn, "/testpath", true, 4, "")
  276. // Meta data is 3*16=48 bytes - text is 40 bytes, padding is 8 bytes
  277. if testConn.Out.String() != ("ICY 200 OK\r\n" +
  278. "Content-Type: Test/Content\r\n" +
  279. "icy-name: TestPlaylist\r\n" +
  280. "icy-metadata: 1\r\n" +
  281. "icy-metaint: 5\r\n" +
  282. "\r\n" +
  283. `56701` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  284. `23456` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  285. `78912` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  286. `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  287. `01234` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  288. `56789` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  289. `12345` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  290. `67012` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  291. `34567` + string(0x03) + `StreamTitle='A very long title name wh';` + string([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}) +
  292. `89`) {
  293. t.Error("Unexpected response:", testConn.Out.String())
  294. return
  295. }
  296. // Test client close connection
  297. tpl.fp = 0
  298. drh = NewDefaultRequestHandler(&testPlaylistFactory{tpl}, false, false, "")
  299. testConn = &testutil.ErrorTestingConnection{}
  300. testConn.OutClose = true
  301. out.Reset()
  302. drh.defaultServeRequest(testConn, "/testpath", true, 0, "")
  303. if out.String() != "Serve request path:/testpath Metadata support:true Offset:0\n"+
  304. "Written bytes: 0\n"+
  305. "Sending: A very long title name which should be truncated - Test Artist\n"+
  306. "Could not write to client - closing connection\n" {
  307. t.Error("Unexpected output:", out.String())
  308. return
  309. }
  310. }
  311. func TestRequestHandling(t *testing.T) {
  312. DebugOutput = true
  313. var out bytes.Buffer
  314. // Collect the print output
  315. Print = func(v ...interface{}) {
  316. out.WriteString(fmt.Sprint(v...))
  317. out.WriteString("\n")
  318. }
  319. defer func() {
  320. Print = log.Print
  321. }()
  322. drh := NewDefaultRequestHandler(nil, false, false, "")
  323. testConn := &testutil.ErrorTestingConnection{}
  324. // Check normal error return
  325. drh.HandleRequest(testConn, &testNetError{})
  326. if out.String() != "Handling request from: <nil>\n"+
  327. "TestNetError\n" {
  328. t.Error("Unexpected output:", out.String())
  329. return
  330. }
  331. out.Reset()
  332. // Test connection writing errors
  333. testConn = &testutil.ErrorTestingConnection{}
  334. for i := 0; i < 1600; i++ {
  335. testConn.In.WriteString("0123456789")
  336. }
  337. testConn.InErr = 530
  338. drh.HandleRequest(testConn, nil)
  339. if out.String() != "Handling request from: <nil>\n"+
  340. "Test reading error\n" {
  341. t.Error("Unexpected output:", out.String())
  342. return
  343. }
  344. out.Reset()
  345. testConn.In.Reset()
  346. for i := 0; i < 1600; i++ {
  347. testConn.In.WriteString("0123456789")
  348. }
  349. testConn.InErr = 0
  350. drh.HandleRequest(testConn, nil)
  351. if out.String() != "Handling request from: <nil>\n"+
  352. "Illegal request: Request is too long\n" {
  353. t.Error("Unexpected output:", out.String())
  354. return
  355. }
  356. out.Reset()
  357. testConn.In.Reset()
  358. testConn.In.WriteString("123")
  359. testConn.InErr = 0
  360. drh.HandleRequest(testConn, nil)
  361. if out.String() != "Handling request from: <nil>\n"+
  362. "Client:<nil> Request:123\r\n\r\n\n"+
  363. "Invalid request: 123\n" {
  364. t.Error("Unexpected output:", out.String())
  365. return
  366. }
  367. // Test auth
  368. drh = NewDefaultRequestHandler(nil, false, false, "web:web")
  369. testConn = &testutil.ErrorTestingConnection{}
  370. testConn.In.Reset()
  371. testConn.In.WriteString(testRequest5)
  372. // Check normal error return
  373. drh.HandleRequest(testConn, nil)
  374. if !strings.Contains(out.String(), "Invalid request (cannot decode authentication)") {
  375. t.Error("Unexpected output:", out.String())
  376. return
  377. }
  378. out.Reset()
  379. testConn.In.Reset()
  380. testConn.In.WriteString(testRequest2)
  381. // Check normal error return
  382. drh.HandleRequest(testConn, nil)
  383. if !strings.Contains(out.String(), "No authentication found") {
  384. t.Error("Unexpected output:", out.String())
  385. return
  386. }
  387. out.Reset()
  388. drh = NewDefaultRequestHandler(nil, false, false, "web:web2")
  389. testConn = &testutil.ErrorTestingConnection{}
  390. testConn.In.Reset()
  391. testConn.In.WriteString(testRequest3)
  392. // Check normal error return
  393. drh.HandleRequest(testConn, nil)
  394. if !strings.Contains(out.String(), "Wrong authentication:web:web") {
  395. t.Error("Unexpected output:", out.String())
  396. return
  397. }
  398. out.Reset()
  399. }
  400. func TestRequestHandler(t *testing.T) {
  401. DebugOutput = true
  402. var out bytes.Buffer
  403. // Collect the print output
  404. Print = func(v ...interface{}) {
  405. out.WriteString(fmt.Sprint(v...))
  406. out.WriteString("\n")
  407. }
  408. defer func() {
  409. Print = log.Print
  410. }()
  411. drh := NewDefaultRequestHandler(nil, false, false, "")
  412. dds := NewServer(drh.HandleRequest)
  413. var wg sync.WaitGroup
  414. wg.Add(1)
  415. go func() {
  416. err := dds.Run(testport, &wg)
  417. if err != nil {
  418. t.Error(err)
  419. return
  420. }
  421. }()
  422. wg.Wait()
  423. rpath := ""
  424. rmetaDataSupport := false
  425. roffset := -1
  426. rauth := ""
  427. errorChan := make(chan error)
  428. drh.ServeRequest = func(c net.Conn, path string, metaDataSupport bool, offset int, auth string) {
  429. rpath = path
  430. rmetaDataSupport = metaDataSupport
  431. roffset = offset
  432. rauth = auth
  433. errorChan <- nil
  434. }
  435. defer func() {
  436. drh.ServeRequest = drh.defaultServeRequest
  437. }()
  438. // Server is now running
  439. if err := writeSocket([]byte(testRequest)); err != nil {
  440. t.Error(err)
  441. return
  442. }
  443. <-errorChan
  444. if rpath != "/mylist" || rmetaDataSupport != true || roffset != 0 || rauth != "" {
  445. t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset)
  446. return
  447. }
  448. if err := writeSocket([]byte(testRequest2)); err != nil {
  449. t.Error(err)
  450. return
  451. }
  452. <-errorChan
  453. if rpath != "/mylist2" || rmetaDataSupport != true || roffset != 656 || rauth != "" {
  454. t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset)
  455. return
  456. }
  457. if err := writeSocket([]byte(testRequest3)); err != nil {
  458. t.Error(err)
  459. return
  460. }
  461. <-errorChan
  462. if rpath != "/bach/cello_suite1" || rmetaDataSupport != false || roffset != 0 || rauth != "web:web" {
  463. t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset, rauth)
  464. return
  465. }
  466. if err := writeSocket([]byte(testRequest4)); err != nil {
  467. t.Error(err)
  468. return
  469. }
  470. <-errorChan
  471. if rpath != "/bach/cello_suite1" || rmetaDataSupport != false || roffset != 0 || rauth != "web:web" {
  472. t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset, rauth)
  473. fmt.Println(testRequest4)
  474. return
  475. }
  476. if err := writeSocket([]byte("\r\n")); err != nil {
  477. t.Error(err)
  478. return
  479. }
  480. <-errorChan
  481. if rpath != "/bach/cello_suite1" || rmetaDataSupport != false || roffset != 0 || rauth != "web:web" {
  482. t.Error("Unexpected request decoding result:", rpath, rmetaDataSupport, roffset, rauth)
  483. fmt.Println(testRequest4)
  484. return
  485. }
  486. // Shutdown server
  487. wg.Add(1)
  488. dds.Shutdown()
  489. wg.Wait()
  490. }
  491. func writeSocket(req []byte) error {
  492. conn, err := net.Dial("tcp", testport)
  493. if err != nil {
  494. return err
  495. }
  496. defer conn.Close()
  497. conn.Write(req)
  498. return nil
  499. }