2
0

server.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. #!/usr/bin/python
  2. '''
  3. The MIT License (MIT)
  4. Copyright (c) 2013-2015 SRS(ossrs)
  5. Permission is hereby granted, free of charge, to any person obtaining a copy of
  6. this software and associated documentation files (the "Software"), to deal in
  7. the Software without restriction, including without limitation the rights to
  8. use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  9. the Software, and to permit persons to whom the Software is furnished to do so,
  10. subject to the following conditions:
  11. The above copyright notice and this permission notice shall be included in all
  12. copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  15. FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  16. COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  17. IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  18. CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  19. '''
  20. """
  21. the api-server is a default demo server for srs to call
  22. when srs get some event, for example, when client connect
  23. to srs, srs can invoke the http api of the api-server
  24. """
  25. import sys
  26. # reload sys model to enable the getdefaultencoding method.
  27. reload(sys)
  28. # set the default encoding to utf-8
  29. # using exec to set the encoding, to avoid error in IDE.
  30. exec("sys.setdefaultencoding('utf-8')")
  31. assert sys.getdefaultencoding().lower() == "utf-8"
  32. import os, json, time, datetime, cherrypy, threading, urllib2
  33. # simple log functions.
  34. def trace(msg):
  35. date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  36. print "[%s][trace] %s"%(date, msg)
  37. # enable crossdomain access for js-client
  38. # define the following method:
  39. # def OPTIONS(self, *args, **kwargs)
  40. # enable_crossdomain()
  41. # invoke this method to enable js to request crossdomain.
  42. def enable_crossdomain():
  43. cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"
  44. cherrypy.response.headers["Access-Control-Allow-Methods"] = "GET, POST, HEAD, PUT, DELETE"
  45. # generate allow headers for crossdomain.
  46. allow_headers = ["Cache-Control", "X-Proxy-Authorization", "X-Requested-With", "Content-Type"]
  47. cherrypy.response.headers["Access-Control-Allow-Headers"] = ",".join(allow_headers)
  48. # error codes definition
  49. class Error:
  50. # ok, success, completed.
  51. success = 0
  52. # error when parse json
  53. system_parse_json = 100
  54. # request action invalid
  55. request_invalid_action = 200
  56. # cdn node not exists
  57. cdn_node_not_exists = 201
  58. '''
  59. handle the clients requests: connect/disconnect vhost/app.
  60. '''
  61. class RESTClients(object):
  62. exposed = True
  63. def GET(self):
  64. enable_crossdomain()
  65. clients = {}
  66. return json.dumps(clients)
  67. '''
  68. for SRS hook: on_connect/on_close
  69. on_connect:
  70. when client connect to vhost/app, call the hook,
  71. the request in the POST data string is a object encode by json:
  72. {
  73. "action": "on_connect",
  74. "client_id": 1985,
  75. "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
  76. "tcUrl": "rtmp://video.test.com/live?key=d2fa801d08e3f90ed1e1670e6e52651a",
  77. "pageUrl": "http://www.test.com/live.html"
  78. }
  79. on_close:
  80. when client close/disconnect to vhost/app/stream, call the hook,
  81. the request in the POST data string is a object encode by json:
  82. {
  83. "action": "on_close",
  84. "client_id": 1985,
  85. "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
  86. "send_bytes": 10240, "recv_bytes": 10240
  87. }
  88. if valid, the hook must return HTTP code 200(Stauts OK) and response
  89. an int value specifies the error code(0 corresponding to success):
  90. 0
  91. '''
  92. def POST(self):
  93. enable_crossdomain()
  94. # return the error code in str
  95. code = Error.success
  96. req = cherrypy.request.body.read()
  97. trace("post to clients, req=%s"%(req))
  98. try:
  99. json_req = json.loads(req)
  100. except Exception, ex:
  101. code = Error.system_parse_json
  102. trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  103. return str(code)
  104. action = json_req["action"]
  105. if action == "on_connect":
  106. code = self.__on_connect(json_req)
  107. elif action == "on_close":
  108. code = self.__on_close(json_req)
  109. else:
  110. trace("invalid request action: %s"%(json_req["action"]))
  111. code = Error.request_invalid_action
  112. return str(code)
  113. def OPTIONS(self, *args, **kwargs):
  114. enable_crossdomain()
  115. def __on_connect(self, req):
  116. code = Error.success
  117. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, tcUrl=%s, pageUrl=%s"%(
  118. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["tcUrl"], req["pageUrl"]
  119. ))
  120. # TODO: process the on_connect event
  121. return code
  122. def __on_close(self, req):
  123. code = Error.success
  124. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, send_bytes=%s, recv_bytes=%s"%(
  125. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["send_bytes"], req["recv_bytes"]
  126. ))
  127. # TODO: process the on_close event
  128. return code
  129. '''
  130. handle the streams requests: publish/unpublish stream.
  131. '''
  132. class RESTStreams(object):
  133. exposed = True
  134. def GET(self):
  135. enable_crossdomain()
  136. streams = {}
  137. return json.dumps(streams)
  138. '''
  139. for SRS hook: on_publish/on_unpublish
  140. on_publish:
  141. when client(encoder) publish to vhost/app/stream, call the hook,
  142. the request in the POST data string is a object encode by json:
  143. {
  144. "action": "on_publish",
  145. "client_id": 1985,
  146. "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
  147. "stream": "livestream"
  148. }
  149. on_unpublish:
  150. when client(encoder) stop publish to vhost/app/stream, call the hook,
  151. the request in the POST data string is a object encode by json:
  152. {
  153. "action": "on_unpublish",
  154. "client_id": 1985,
  155. "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
  156. "stream": "livestream"
  157. }
  158. if valid, the hook must return HTTP code 200(Stauts OK) and response
  159. an int value specifies the error code(0 corresponding to success):
  160. 0
  161. '''
  162. def POST(self):
  163. enable_crossdomain()
  164. # return the error code in str
  165. code = Error.success
  166. req = cherrypy.request.body.read()
  167. trace("post to streams, req=%s"%(req))
  168. try:
  169. json_req = json.loads(req)
  170. except Exception, ex:
  171. code = Error.system_parse_json
  172. trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  173. return str(code)
  174. action = json_req["action"]
  175. if action == "on_publish":
  176. code = self.__on_publish(json_req)
  177. elif action == "on_unpublish":
  178. code = self.__on_unpublish(json_req)
  179. else:
  180. trace("invalid request action: %s"%(json_req["action"]))
  181. code = Error.request_invalid_action
  182. return str(code)
  183. def OPTIONS(self, *args, **kwargs):
  184. enable_crossdomain()
  185. def __on_publish(self, req):
  186. code = Error.success
  187. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%(
  188. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"]
  189. ))
  190. # TODO: process the on_publish event
  191. return code
  192. def __on_unpublish(self, req):
  193. code = Error.success
  194. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%(
  195. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"]
  196. ))
  197. # TODO: process the on_unpublish event
  198. return code
  199. '''
  200. handle the dvrs requests: dvr stream.
  201. '''
  202. class RESTDvrs(object):
  203. exposed = True
  204. def GET(self):
  205. enable_crossdomain()
  206. dvrs = {}
  207. return json.dumps(dvrs)
  208. '''
  209. for SRS hook: on_dvr
  210. on_dvr:
  211. when srs reap a dvr file, call the hook,
  212. the request in the POST data string is a object encode by json:
  213. {
  214. "action": "on_dvr",
  215. "client_id": 1985,
  216. "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
  217. "stream": "livestream",
  218. "cwd": "/usr/local/srs",
  219. "file": "./objs/nginx/html/live/livestream.1420254068776.flv"
  220. }
  221. if valid, the hook must return HTTP code 200(Stauts OK) and response
  222. an int value specifies the error code(0 corresponding to success):
  223. 0
  224. '''
  225. def POST(self):
  226. enable_crossdomain()
  227. # return the error code in str
  228. code = Error.success
  229. req = cherrypy.request.body.read()
  230. trace("post to dvrs, req=%s"%(req))
  231. try:
  232. json_req = json.loads(req)
  233. except Exception, ex:
  234. code = Error.system_parse_json
  235. trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  236. return str(code)
  237. action = json_req["action"]
  238. if action == "on_dvr":
  239. code = self.__on_dvr(json_req)
  240. else:
  241. trace("invalid request action: %s"%(json_req["action"]))
  242. code = Error.request_invalid_action
  243. return str(code)
  244. def OPTIONS(self, *args, **kwargs):
  245. enable_crossdomain()
  246. def __on_dvr(self, req):
  247. code = Error.success
  248. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, cwd=%s, file=%s"%(
  249. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"],
  250. req["cwd"], req["file"]
  251. ))
  252. # TODO: process the on_dvr event
  253. return code
  254. '''
  255. handle the hls proxy requests: hls stream.
  256. '''
  257. class RESTProxy(object):
  258. exposed = True
  259. '''
  260. for SRS hook: on_hls_notify
  261. on_hls_notify:
  262. when srs reap a ts file of hls, call this hook,
  263. used to push file to cdn network, by get the ts file from cdn network.
  264. so we use HTTP GET and use the variable following:
  265. [app], replace with the app.
  266. [stream], replace with the stream.
  267. [ts_url], replace with the ts url.
  268. ignore any return data of server.
  269. '''
  270. def GET(self, *args, **kwargs):
  271. enable_crossdomain()
  272. url = "http://" + "/".join(args);
  273. print "start to proxy url: %s"%url
  274. f = None
  275. try:
  276. f = urllib2.urlopen(url)
  277. f.read()
  278. except:
  279. print "error proxy url: %s"%url
  280. finally:
  281. if f: f.close()
  282. print "completed proxy url: %s"%url
  283. return url
  284. '''
  285. handle the hls requests: hls stream.
  286. '''
  287. class RESTHls(object):
  288. exposed = True
  289. '''
  290. for SRS hook: on_hls_notify
  291. on_hls_notify:
  292. when srs reap a ts file of hls, call this hook,
  293. used to push file to cdn network, by get the ts file from cdn network.
  294. so we use HTTP GET and use the variable following:
  295. [app], replace with the app.
  296. [stream], replace with the stream.
  297. [ts_url], replace with the ts url.
  298. ignore any return data of server.
  299. '''
  300. def GET(self, *args, **kwargs):
  301. enable_crossdomain()
  302. hls = {
  303. "args": args,
  304. "kwargs": kwargs
  305. }
  306. return json.dumps(hls)
  307. '''
  308. for SRS hook: on_hls
  309. on_hls:
  310. when srs reap a dvr file, call the hook,
  311. the request in the POST data string is a object encode by json:
  312. {
  313. "action": "on_dvr",
  314. "client_id": 1985,
  315. "ip": "192.168.1.10",
  316. "vhost": "video.test.com",
  317. "app": "live",
  318. "stream": "livestream",
  319. "duration": 9.68, // in seconds
  320. "cwd": "/usr/local/srs",
  321. "file": "./objs/nginx/html/live/livestream.1420254068776-100.ts",
  322. "seq_no": 100
  323. }
  324. if valid, the hook must return HTTP code 200(Stauts OK) and response
  325. an int value specifies the error code(0 corresponding to success):
  326. 0
  327. '''
  328. def POST(self):
  329. enable_crossdomain()
  330. # return the error code in str
  331. code = Error.success
  332. req = cherrypy.request.body.read()
  333. trace("post to hls, req=%s"%(req))
  334. try:
  335. json_req = json.loads(req)
  336. except Exception, ex:
  337. code = Error.system_parse_json
  338. trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  339. return str(code)
  340. action = json_req["action"]
  341. if action == "on_hls":
  342. code = self.__on_hls(json_req)
  343. else:
  344. trace("invalid request action: %s"%(json_req["action"]))
  345. code = Error.request_invalid_action
  346. return str(code)
  347. def OPTIONS(self, *args, **kwargs):
  348. enable_crossdomain()
  349. def __on_hls(self, req):
  350. code = Error.success
  351. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, duration=%s, cwd=%s, file=%s, seq_no=%s"%(
  352. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["duration"],
  353. req["cwd"], req["file"], req["seq_no"]
  354. ))
  355. # TODO: process the on_hls event
  356. return code
  357. '''
  358. handle the sessions requests: client play/stop stream
  359. '''
  360. class RESTSessions(object):
  361. exposed = True
  362. def GET(self):
  363. enable_crossdomain()
  364. sessions = {}
  365. return json.dumps(sessions)
  366. '''
  367. for SRS hook: on_play/on_stop
  368. on_play:
  369. when client(encoder) publish to vhost/app/stream, call the hook,
  370. the request in the POST data string is a object encode by json:
  371. {
  372. "action": "on_play",
  373. "client_id": 1985,
  374. "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
  375. "stream": "livestream",
  376. "pageUrl": "http://www.test.com/live.html"
  377. }
  378. on_stop:
  379. when client(encoder) stop publish to vhost/app/stream, call the hook,
  380. the request in the POST data string is a object encode by json:
  381. {
  382. "action": "on_stop",
  383. "client_id": 1985,
  384. "ip": "192.168.1.10", "vhost": "video.test.com", "app": "live",
  385. "stream": "livestream"
  386. }
  387. if valid, the hook must return HTTP code 200(Stauts OK) and response
  388. an int value specifies the error code(0 corresponding to success):
  389. 0
  390. '''
  391. def POST(self):
  392. enable_crossdomain()
  393. # return the error code in str
  394. code = Error.success
  395. req = cherrypy.request.body.read()
  396. trace("post to sessions, req=%s"%(req))
  397. try:
  398. json_req = json.loads(req)
  399. except Exception, ex:
  400. code = Error.system_parse_json
  401. trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  402. return str(code)
  403. action = json_req["action"]
  404. if action == "on_play":
  405. code = self.__on_play(json_req)
  406. elif action == "on_stop":
  407. code = self.__on_stop(json_req)
  408. else:
  409. trace("invalid request action: %s"%(json_req["action"]))
  410. code = Error.request_invalid_action
  411. return str(code)
  412. def OPTIONS(self, *args, **kwargs):
  413. enable_crossdomain()
  414. def __on_play(self, req):
  415. code = Error.success
  416. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s, pageUrl=%s"%(
  417. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"], req["pageUrl"]
  418. ))
  419. # TODO: process the on_play event
  420. return code
  421. def __on_stop(self, req):
  422. code = Error.success
  423. trace("srs %s: client id=%s, ip=%s, vhost=%s, app=%s, stream=%s"%(
  424. req["action"], req["client_id"], req["ip"], req["vhost"], req["app"], req["stream"]
  425. ))
  426. # TODO: process the on_stop event
  427. return code
  428. global_arm_server_id = os.getpid();
  429. class ArmServer:
  430. def __init__(self):
  431. global global_arm_server_id
  432. global_arm_server_id += 1
  433. self.id = str(global_arm_server_id)
  434. self.ip = None
  435. self.device_id = None
  436. self.summaries = None
  437. self.devices = None
  438. self.public_ip = cherrypy.request.remote.ip
  439. self.heartbeat = time.time()
  440. self.clients = 0
  441. def dead(self):
  442. dead_time_seconds = 20
  443. if time.time() - self.heartbeat > dead_time_seconds:
  444. return True
  445. return False
  446. def json_dump(self):
  447. data = {}
  448. data["id"] = self.id
  449. data["ip"] = self.ip
  450. data["device_id"] = self.device_id
  451. data["summaries"] = self.summaries
  452. data["devices"] = self.devices
  453. data["public_ip"] = self.public_ip
  454. data["heartbeat"] = self.heartbeat
  455. data["heartbeat_h"] = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(self.heartbeat))
  456. data["api"] = "http://%s:1985/api/v1/summaries"%(self.ip)
  457. data["console"] = "http://ossrs.net/console/ng_index.html#/summaries?host=%s&port=1985"%(self.ip)
  458. return data
  459. '''
  460. the server list
  461. '''
  462. class RESTServers(object):
  463. exposed = True
  464. def __init__(self):
  465. self.__nodes = []
  466. self.__last_update = datetime.datetime.now();
  467. self.__lock = threading.Lock()
  468. def __get_node(self, device_id):
  469. for node in self.__nodes:
  470. if node.device_id == device_id:
  471. return node
  472. return None
  473. def __refresh_nodes(self):
  474. while len(self.__nodes) > 0:
  475. has_dead_node = False
  476. for node in self.__nodes:
  477. if node.dead():
  478. self.__nodes.remove(node)
  479. has_dead_node = True
  480. if not has_dead_node:
  481. break
  482. '''
  483. post to update server ip.
  484. request body: the new raspberry-pi server ip. TODO: FIXME: more info.
  485. '''
  486. def POST(self):
  487. enable_crossdomain()
  488. try:
  489. self.__lock.acquire()
  490. req = cherrypy.request.body.read()
  491. trace("post to nodes, req=%s"%(req))
  492. try:
  493. json_req = json.loads(req)
  494. except Exception, ex:
  495. code = Error.system_parse_json
  496. trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  497. return json.dumps({"code":code, "data": None})
  498. device_id = json_req["device_id"]
  499. node = self.__get_node(device_id)
  500. if node is None:
  501. node = ArmServer()
  502. self.__nodes.append(node)
  503. node.ip = json_req["ip"]
  504. if "summaries" in json_req:
  505. node.summaries = json_req["summaries"]
  506. if "devices" in json_req:
  507. node.devices = json_req["devices"]
  508. node.device_id = device_id
  509. node.public_ip = cherrypy.request.remote.ip
  510. node.heartbeat = time.time()
  511. return json.dumps({"code":Error.success, "data": {"id":node.id}})
  512. finally:
  513. self.__lock.release()
  514. '''
  515. get all servers which report to this api-server.
  516. '''
  517. def GET(self, id=None):
  518. enable_crossdomain()
  519. try:
  520. self.__lock.acquire()
  521. self.__refresh_nodes()
  522. data = []
  523. for node in self.__nodes:
  524. if id == None or node.id == str(id) or node.device_id == str(id):
  525. data.append(node.json_dump())
  526. return json.dumps(data)
  527. finally:
  528. self.__lock.release()
  529. def DELETE(self, id):
  530. enable_crossdomain()
  531. raise cherrypy.HTTPError(405, "Not allowed.")
  532. def PUT(self, id):
  533. enable_crossdomain()
  534. raise cherrypy.HTTPError(405, "Not allowed.")
  535. def OPTIONS(self, *args, **kwargs):
  536. enable_crossdomain()
  537. global_chat_id = os.getpid();
  538. '''
  539. the chat streams, public chat room.
  540. '''
  541. class RESTChats(object):
  542. exposed = True
  543. global_id = 100
  544. def __init__(self):
  545. # object fields:
  546. # id: an int value indicates the id of user.
  547. # username: a str indicates the user name.
  548. # url: a str indicates the url of user stream.
  549. # agent: a str indicates the agent of user.
  550. # join_date: a number indicates the join timestamp in seconds.
  551. # join_date_str: a str specifies the formated friendly time.
  552. # heatbeat: a number indicates the heartbeat timestamp in seconds.
  553. # vcodec: a dict indicates the video codec info.
  554. # acodec: a dict indicates the audio codec info.
  555. self.__chats = [];
  556. self.__chat_lock = threading.Lock();
  557. # dead time in seconds, if exceed, remove the chat.
  558. self.__dead_time = 15;
  559. '''
  560. get the rtmp url of chat object. None if overflow.
  561. '''
  562. def get_url_by_index(self, index):
  563. index = int(index)
  564. if index is None or index >= len(self.__chats):
  565. return None;
  566. return self.__chats[index]["url"];
  567. def GET(self):
  568. enable_crossdomain()
  569. try:
  570. self.__chat_lock.acquire();
  571. chats = [];
  572. copy = self.__chats[:];
  573. for chat in copy:
  574. if time.time() - chat["heartbeat"] > self.__dead_time:
  575. self.__chats.remove(chat);
  576. continue;
  577. chats.append({
  578. "id": chat["id"],
  579. "username": chat["username"],
  580. "url": chat["url"],
  581. "join_date_str": chat["join_date_str"],
  582. "heartbeat": chat["heartbeat"],
  583. });
  584. finally:
  585. self.__chat_lock.release();
  586. return json.dumps({"code":0, "data": {"now": time.time(), "chats": chats}})
  587. def POST(self):
  588. enable_crossdomain()
  589. req = cherrypy.request.body.read()
  590. chat = json.loads(req)
  591. global global_chat_id;
  592. chat["id"] = global_chat_id
  593. global_chat_id += 1
  594. chat["join_date"] = time.time();
  595. chat["heartbeat"] = time.time();
  596. chat["join_date_str"] = time.strftime("%Y-%m-%d %H:%M:%S");
  597. try:
  598. self.__chat_lock.acquire();
  599. self.__chats.append(chat)
  600. finally:
  601. self.__chat_lock.release();
  602. trace("create chat success, id=%s"%(chat["id"]))
  603. return json.dumps({"code":0, "data": chat["id"]})
  604. def DELETE(self, id):
  605. enable_crossdomain()
  606. try:
  607. self.__chat_lock.acquire();
  608. for chat in self.__chats:
  609. if str(id) != str(chat["id"]):
  610. continue
  611. self.__chats.remove(chat)
  612. trace("delete chat success, id=%s"%(id))
  613. return json.dumps({"code":0, "data": None})
  614. finally:
  615. self.__chat_lock.release();
  616. raise cherrypy.HTTPError(405, "Not allowed.")
  617. def PUT(self, id):
  618. enable_crossdomain()
  619. try:
  620. self.__chat_lock.acquire();
  621. for chat in self.__chats:
  622. if str(id) != str(chat["id"]):
  623. continue
  624. chat["heartbeat"] = time.time();
  625. trace("heartbeat chat success, id=%s"%(id))
  626. return json.dumps({"code":0, "data": None})
  627. finally:
  628. self.__chat_lock.release();
  629. raise cherrypy.HTTPError(405, "Not allowed.")
  630. def OPTIONS(self, *args, **kwargs):
  631. enable_crossdomain()
  632. # HTTP RESTful path.
  633. class Root(object):
  634. exposed = True
  635. def __init__(self):
  636. self.api = Api()
  637. def GET(self):
  638. enable_crossdomain();
  639. return json.dumps({"code":Error.success, "urls":{"api":"the api root"}})
  640. def OPTIONS(self, *args, **kwargs):
  641. enable_crossdomain();
  642. # HTTP RESTful path.
  643. class Api(object):
  644. exposed = True
  645. def __init__(self):
  646. self.v1 = V1()
  647. def GET(self):
  648. enable_crossdomain();
  649. return json.dumps({"code":Error.success,
  650. "urls": {
  651. "v1": "the api version 1.0"
  652. }
  653. });
  654. def OPTIONS(self, *args, **kwargs):
  655. enable_crossdomain();
  656. # HTTP RESTful path. to access as:
  657. # http://127.0.0.1:8085/api/v1/clients
  658. class V1(object):
  659. exposed = True
  660. def __init__(self):
  661. self.clients = RESTClients()
  662. self.streams = RESTStreams()
  663. self.sessions = RESTSessions()
  664. self.dvrs = RESTDvrs()
  665. self.hls = RESTHls()
  666. self.proxy = RESTProxy()
  667. self.chats = RESTChats()
  668. self.servers = RESTServers()
  669. def GET(self):
  670. enable_crossdomain();
  671. return json.dumps({"code":Error.success, "urls":{
  672. "clients": "for srs http callback, to handle the clients requests: connect/disconnect vhost/app.",
  673. "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.",
  674. "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream",
  675. "dvrs": "for srs http callback, to handle the dvr requests: dvr stream.",
  676. "chats": "for srs demo meeting, the chat streams, public chat room.",
  677. "servers": {
  678. "summary": "for srs raspberry-pi and meeting demo",
  679. "GET": "get the current raspberry-pi servers info",
  680. "POST ip=node_ip&device_id=device_id": "the new raspberry-pi server info."
  681. }
  682. }});
  683. def OPTIONS(self, *args, **kwargs):
  684. enable_crossdomain();
  685. '''
  686. main code start.
  687. '''
  688. # donot support use this module as library.
  689. if __name__ != "__main__":
  690. raise Exception("embed not support")
  691. # check the user options
  692. if len(sys.argv) <= 1:
  693. print "SRS api callback server, Copyright (c) 2013-2015 SRS(ossrs)"
  694. print "Usage: python %s <port>"%(sys.argv[0])
  695. print " port: the port to listen at."
  696. print "For example:"
  697. print " python %s 8085"%(sys.argv[0])
  698. print ""
  699. print "See also: https://github.com/ossrs/srs"
  700. sys.exit(1)
  701. # parse port from user options.
  702. port = int(sys.argv[1])
  703. static_dir = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), "static-dir"))
  704. trace("api server listen at port: %s, static_dir: %s"%(port, static_dir))
  705. # cherrypy config.
  706. conf = {
  707. 'global': {
  708. 'server.shutdown_timeout': 1,
  709. 'server.socket_host': '0.0.0.0',
  710. 'server.socket_port': port,
  711. 'tools.encode.on': True,
  712. 'tools.staticdir.on': True,
  713. 'tools.encode.encoding': "utf-8",
  714. #'server.thread_pool': 2, # single thread server.
  715. },
  716. '/': {
  717. 'tools.staticdir.dir': static_dir,
  718. 'tools.staticdir.index': "index.html",
  719. # for cherrypy RESTful api support
  720. 'request.dispatch': cherrypy.dispatch.MethodDispatcher()
  721. }
  722. }
  723. # start cherrypy web engine
  724. trace("start cherrypy server")
  725. root = Root()
  726. cherrypy.quickstart(root, '/', conf)