winlin

add nodes for api

@@ -63,6 +63,8 @@ class Error: @@ -63,6 +63,8 @@ class Error:
63 system_parse_json = 100 63 system_parse_json = 100
64 # request action invalid 64 # request action invalid
65 request_invalid_action = 200 65 request_invalid_action = 200
  66 + # cdn node not exists
  67 + cdn_node_not_exists = 201
66 68
67 ''' 69 '''
68 handle the clients requests: connect/disconnect vhost/app. 70 handle the clients requests: connect/disconnect vhost/app.
@@ -403,12 +405,186 @@ class RESTServers(object): @@ -403,12 +405,186 @@ class RESTServers(object):
403 enable_crossdomain() 405 enable_crossdomain()
404 406
405 def __generate_hls(self, hls_url): 407 def __generate_hls(self, hls_url):
  408 + return SrsUtility().hls_html(hls_url)
  409 +
  410 +class SrsUtility:
  411 + def hls_html(self, hls_url):
406 return """ 412 return """
  413 +<h1>%s</h1>
407 <video width="640" height="360" 414 <video width="640" height="360"
408 autoplay controls autobuffer 415 autoplay controls autobuffer
409 src="%s" 416 src="%s"
410 type="application/vnd.apple.mpegurl"> 417 type="application/vnd.apple.mpegurl">
411 -</video>"""%(hls_url); 418 +</video>"""%(hls_url, hls_url);
  419 +
  420 +global_cdn_id = os.getpid();
  421 +class CdnNode:
  422 + def __init__(self):
  423 + global global_cdn_id
  424 + global_cdn_id += 1
  425 +
  426 + self.id = str(global_cdn_id)
  427 + self.ip = None
  428 + self.os = None
  429 + self.srs_status = None
  430 +
  431 + self.public_ip = cherrypy.request.remote.ip
  432 + self.heartbeat = time.time()
  433 +
  434 + def dead(self):
  435 + dead_time_seconds = 10
  436 + if time.time() - self.heartbeat > dead_time_seconds:
  437 + return True
  438 + return False
  439 +
  440 + def json_dump(self):
  441 + data = {}
  442 + data["id"] = self.id
  443 + data["ip"] = self.ip
  444 + data["os"] = self.os
  445 + data["srs_status"] = self.srs_status
  446 + data["public_ip"] = self.public_ip
  447 + data["heartbeat"] = self.heartbeat
  448 + data["heartbeat_h"] = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime(self.heartbeat))
  449 + return data
  450 +
  451 +'''
  452 +the cdn nodes list
  453 +'''
  454 +class RESTNodes(object):
  455 + exposed = True
  456 +
  457 + def __init__(self):
  458 + self.__nodes = []
  459 +
  460 + def __get_node(self, id):
  461 + for node in self.__nodes:
  462 + if node.id == id:
  463 + return node
  464 + return None
  465 +
  466 + def __refresh_nodes(self):
  467 + has_dead_node = False
  468 + while True:
  469 + for node in self.__nodes:
  470 + if node.dead():
  471 + self.__nodes.remove(node)
  472 + has_dead_node = True
  473 + if not has_dead_node:
  474 + break
  475 +
  476 + def __get_peers(self, target_node):
  477 + peers = []
  478 + for node in self.__nodes:
  479 + if node.id == target_node.id:
  480 + continue
  481 + if node.public_ip == target_node.public_ip:
  482 + peers.append(node)
  483 + return peers
  484 +
  485 + def __get_peers_for_play(self, ip):
  486 + peers = []
  487 + for node in self.__nodes:
  488 + if node.public_ip == ip and node.srs_status == "running":
  489 + peers.append(node)
  490 + return peers
  491 +
  492 + def __json_dump_nodes(self, peers):
  493 + data = []
  494 + for node in peers:
  495 + data.append(node.json_dump())
  496 + return data
  497 +
  498 + def GET(self, type=None, format=None, origin=None, vhost=None, port=None, stream=None):
  499 + enable_crossdomain()
  500 +
  501 + self.__refresh_nodes()
  502 + data = self.__json_dump_nodes(self.__nodes)
  503 +
  504 + ip = cherrypy.request.remote.ip
  505 + if type is not None:
  506 + server = origin
  507 + peers = self.__get_peers_for_play(ip)
  508 + if len(peers) > 0:
  509 + server = peers[0].ip
  510 + if type == "hls":
  511 + hls_url = "http://%s:%s/%s.m3u8"%(server, port, stream)
  512 + hls_url = hls_url.replace(".m3u8.m3u8", ".m3u8")
  513 + if format == "html":
  514 + return SrsUtility().hls_html(hls_url)
  515 + else:
  516 + #return hls_url
  517 + raise cherrypy.HTTPRedirect(hls_url)
  518 + elif type == "rtmp":
  519 + rtmp_url = "rtmp://%s:%s/%s?vhost=%s/%s"%(server, port, stream.split("/")[0], vhost, stream.split("/")[1])
  520 + if format == "html":
  521 + html = "%s?server=%s&port=%s&vhost=%s&app=%s&stream=%s&autostart=true"%(
  522 + "http://demo.chnvideo.com:8085/srs/trunk/research/players/srs_player.html",
  523 + server, port, vhost, stream.split("/")[0], stream.split("/")[1])
  524 + #return html
  525 + raise cherrypy.HTTPRedirect(html)
  526 + return rtmp_url
  527 +
  528 + return json.dumps({"code":Error.success, "data": data})
  529 +
  530 + def PUT(self):
  531 + enable_crossdomain()
  532 +
  533 + req = cherrypy.request.body.read()
  534 + trace("put to nodes, req=%s"%(req))
  535 + try:
  536 + json_req = json.loads(req)
  537 + except Exception, ex:
  538 + code = Error.system_parse_json
  539 + trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  540 + return json.dumps({"code":code, "data": None})
  541 +
  542 + id = str(json_req["id"])
  543 + node = self.__get_node(id)
  544 + if node is None:
  545 + code = Error.cdn_node_not_exists
  546 + trace("cdn node not exists, req=%s, id=%s, code=%s"%(req, id, code))
  547 + return json.dumps({"code":code, "data": None})
  548 +
  549 + node.heartbeat = time.time()
  550 + node.srs_status = str(json_req["srs_status"])
  551 +
  552 + self.__refresh_nodes()
  553 + peers = self.__get_peers(node)
  554 + peers_data = self.__json_dump_nodes(peers)
  555 +
  556 + res = json.dumps({"code":Error.success, "data": {"id":node.id, "peers":peers_data}})
  557 + trace(res)
  558 + return res
  559 +
  560 + def POST(self):
  561 + enable_crossdomain()
  562 +
  563 + req = cherrypy.request.body.read()
  564 + trace("post to nodes, req=%s"%(req))
  565 + try:
  566 + json_req = json.loads(req)
  567 + except Exception, ex:
  568 + code = Error.system_parse_json
  569 + trace("parse the request to json failed, req=%s, ex=%s, code=%s"%(req, ex, code))
  570 + return json.dumps({"code":code, "data": None})
  571 +
  572 + node = CdnNode()
  573 + node.ip = str(json_req["ip"]);
  574 + node.os = str(json_req["os"]);
  575 + node.srs_status = str(json_req["srs_status"])
  576 + self.__nodes.append(node)
  577 +
  578 + self.__refresh_nodes()
  579 + peers = self.__get_peers(node)
  580 + peers_data = self.__json_dump_nodes(peers)
  581 +
  582 + res = json.dumps({"code":Error.success, "data": {"id":node.id, "peers":peers_data}})
  583 + trace(res)
  584 + return res
  585 +
  586 + def OPTIONS(self, *args, **kwargs):
  587 + enable_crossdomain()
412 588
413 global_chat_id = os.getpid(); 589 global_chat_id = os.getpid();
414 ''' 590 '''
@@ -572,6 +748,7 @@ class V1(object): @@ -572,6 +748,7 @@ class V1(object):
572 self.sessions = RESTSessions() 748 self.sessions = RESTSessions()
573 self.chats = RESTChats() 749 self.chats = RESTChats()
574 self.servers = RESTServers() 750 self.servers = RESTServers()
  751 + self.nodes = RESTNodes()
575 def GET(self): 752 def GET(self):
576 enable_crossdomain(); 753 enable_crossdomain();
577 return json.dumps({"code":Error.success, "urls":{ 754 return json.dumps({"code":Error.success, "urls":{
@@ -579,15 +756,20 @@ class V1(object): @@ -579,15 +756,20 @@ class V1(object):
579 "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.", 756 "streams": "for srs http callback, to handle the streams requests: publish/unpublish stream.",
580 "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream", 757 "sessions": "for srs http callback, to handle the sessions requests: client play/stop stream",
581 "chats": "for srs demo meeting, the chat streams, public chat room.", 758 "chats": "for srs demo meeting, the chat streams, public chat room.",
  759 + "nodes": {
  760 + "summary": "for srs cdn node",
  761 + "POST ip=node_ip&os=node_os": "register a new node",
  762 + "GET": "get the active edge nodes",
  763 + "GET type=hls&format=html&origin=demo.chnvideo.com&port=8080&stream=live/livestream": "get the play url, html for hls",
  764 + "GET type=rtmp&format=html&origin=demo.chnvideo.com&vhost=demo.srs.com&port=1935&stream=live/livestream": "get the play url, for rtmp"
  765 + },
582 "servers": { 766 "servers": {
583 "summary": "for srs raspberry-pi and meeting demo", 767 "summary": "for srs raspberry-pi and meeting demo",
584 "GET": "get the current raspberry-pi servers info", 768 "GET": "get the current raspberry-pi servers info",
585 - "POST": {  
586 - "body": "the new raspberry-pi server ip."  
587 - }, 769 + "POST body=ip": "the new raspberry-pi server ip.",
588 "GET id=ingest&action=play&stream=live/livestream": "play the ingest HLS stream on raspberry-pi", 770 "GET id=ingest&action=play&stream=live/livestream": "play the ingest HLS stream on raspberry-pi",
589 "GET id=ingest&action=rtmp&stream=live/livestream": "play the ingest RTMP stream on raspberry-pi", 771 "GET id=ingest&action=rtmp&stream=live/livestream": "play the ingest RTMP stream on raspberry-pi",
590 - "GET id=ingest&action=hls&stream=live/livestream.m3u8": "play the ingest HLS stream on raspberry-pi", 772 + "GET id=ingest&action=hls&stream=live/livestream": "play the ingest HLS stream on raspberry-pi",
591 "GET id=ingest&action=mgmt": "open the HTTP api url of raspberry-pi", 773 "GET id=ingest&action=mgmt": "open the HTTP api url of raspberry-pi",
592 "GET id=meeting": "redirect to local raspberry-pi meeting url(local ignored)", 774 "GET id=meeting": "redirect to local raspberry-pi meeting url(local ignored)",
593 "GET id=meeting&local=false&index=0": "play the first(index=0) meeting HLS stream on demo.chnvideo.com(not local)", 775 "GET id=meeting&local=false&index=0": "play the first(index=0) meeting HLS stream on demo.chnvideo.com(not local)",