nfd-status-http-server.py
Go to the documentation of this file.
1 #!/usr/bin/env python2.7
2 # -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
3 
4 """
5 Copyright (c) 2014 Regents of the University of California,
6  Arizona Board of Regents,
7  Colorado State University,
8  University Pierre & Marie Curie, Sorbonne University,
9  Washington University in St. Louis,
10  Beijing Institute of Technology
11 
12 This file is part of NFD (Named Data Networking Forwarding Daemon).
13 See AUTHORS.md for complete list of NFD authors and contributors.
14 
15 NFD is free software: you can redistribute it and/or modify it under the terms
16 of the GNU General Public License as published by the Free Software Foundation,
17 either version 3 of the License, or (at your option) any later version.
18 
19 NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
20 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
21 PURPOSE. See the GNU General Public License for more details.
22 
23 You should have received a copy of the GNU General Public License along with
24 NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
25 """
26 
27 from BaseHTTPServer import HTTPServer
28 from SimpleHTTPServer import SimpleHTTPRequestHandler
29 from SocketServer import ThreadingMixIn
30 import sys
31 import subprocess
32 import StringIO
33 import urlparse
34 import logging
35 import cgi
36 import argparse
37 import socket
38 import os
39 
40 
41 class StatusHandler(SimpleHTTPRequestHandler):
42  """ The handler class to handle requests."""
43  def do_GET(self):
44  # get the url info to decide how to respond
45  parsedPath = urlparse.urlparse(self.path)
46  if parsedPath.path == "/":
47  # get current nfd status, and use it as result message
48  (res, resultMessage) = self.getNfdStatus()
49  self.send_response(200)
50  if res == 0:
51  self.send_header("Content-type", "text/xml; charset=UTF-8")
52  else:
53  self.send_header("Content-type", "text/html; charset=UTF-8")
54 
55  self.end_headers()
56  self.wfile.write(resultMessage)
57  elif parsedPath.path == "/robots.txt" and self.server.robots == True:
58  resultMessage = ""
59  self.send_response(200)
60  self.send_header("Content-type", "text/plain")
61  self.end_headers()
62  self.wfile.write(resultMessage)
63  else:
64  SimpleHTTPRequestHandler.do_GET(self)
65 
66  def log_message(self, format, *args):
67  if self.server.verbose:
68  logging.info("%s - %s\n" % (self.address_string(),
69  format % args))
70 
71  def getNfdStatus(self):
72  """
73  This function is to call nfd-status command
74  to get xml format output
75  """
76  sp = subprocess.Popen(['nfd-status', '-x'], stdout=subprocess.PIPE, close_fds=True)
77  output = sp.communicate()[0]
78  if sp.returncode == 0:
79  # add the xml-stylesheet processing instruction after the 1st '>' symbol
80  newLineIndex = output.index('>') + 1
81  resultStr = output[:newLineIndex]\
82  + "<?xml-stylesheet type=\"text/xsl\" href=\"nfd-status.xsl\"?>"\
83  + output[newLineIndex:]
84  return (sp.returncode, resultStr)
85  else:
86  htmlStr = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional'\
87  + '//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'\
88  + '<html><head><title>NFD status</title>'\
89  + '<meta http-equiv="Content-type" content="text/html;'\
90  + 'charset=UTF-8"></head>\n<body>'
91  # return connection error code
92  htmlStr = htmlStr + "<p>Cannot connect to NFD,"\
93  + " Code = " + str(sp.returncode) + "</p>\n"
94  htmlStr = htmlStr + "</body></html>"
95  return (sp.returncode, htmlStr)
96 
97 class ThreadHttpServer(ThreadingMixIn, HTTPServer):
98  """ Handle requests using threads """
99  def __init__(self, server, handler, verbose=False, robots=False):
100  serverAddr = server[0]
101  # socket.AF_UNSPEC is not supported, check whether it is v6 or v4
102  ipType = self.getIpType(serverAddr)
103  if ipType == socket.AF_INET6:
104  self.address_family = socket.AF_INET6
105  elif ipType == socket.AF_INET:
106  self.address_family == socket.AF_INET
107  else:
108  logging.error("The input IP address is neither IPv6 nor IPv4")
109  sys.exit(2)
110 
111  try:
112  HTTPServer.__init__(self, server, handler)
113  except Exception as e:
114  logging.error(str(e))
115  sys.exit(2)
116  self.verbose = verbose
117  self.robots = robots
118 
119  def getIpType(self, ipAddr):
120  """ Get ipAddr's address type """
121  # if ipAddr is an IPv6 addr, return AF_INET6
122  try:
123  socket.inet_pton(socket.AF_INET6, ipAddr)
124  return socket.AF_INET6
125  except socket.error:
126  pass
127  # if ipAddr is an IPv4 addr return AF_INET, if not, return None
128  try:
129  socket.inet_pton(socket.AF_INET, ipAddr)
130  return socket.AF_INET
131  except socket.error:
132  return None
133 
134 
135 # main function to start
137  parser = argparse.ArgumentParser()
138  parser.add_argument("-p", type=int, metavar="port number",
139  help="Specify the HTTP server port number, default is 8080.",
140  dest="port", default=8080)
141  # if address is not specified, use 127.0.0.1
142  parser.add_argument("-a", default="127.0.0.1", metavar="IP address", dest="addr",
143  help="Specify the HTTP server IP address.")
144  parser.add_argument("-r", default=False, dest="robots", action="store_true",
145  help="Enable HTTP robots to crawl; disabled by default.")
146  parser.add_argument("-f", default="@DATAROOTDIR@/ndn", metavar="Server Directory", dest="serverDir",
147  help="Specify the working directory of nfd-status-http-server, default is @DATAROOTDIR@/ndn.")
148  parser.add_argument("-v", default=False, dest="verbose", action="store_true",
149  help="Verbose mode.")
150  parser.add_argument("--version", default=False, dest="version", action="store_true",
151  help="Show version and exit")
152 
153  args = vars(parser.parse_args())
154 
155  if args['version']:
156  print "@VERSION@"
157  return
158 
159  localPort = args["port"]
160  localAddr = args["addr"]
161  verbose = args["verbose"]
162  robots = args["robots"]
163  serverDirectory = args["serverDir"]
164 
165  os.chdir(serverDirectory)
166 
167  # setting log message format
168  logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s',
169  level=logging.INFO)
170 
171  # if port is invalid, exit
172  if localPort <= 0 or localPort > 65535:
173  logging.error("Specified port number is invalid")
174  sys.exit(2)
175 
176  httpd = ThreadHttpServer((localAddr, localPort), StatusHandler,
177  verbose, robots)
178  httpServerAddr = ""
179  if httpd.address_family == socket.AF_INET6:
180  httpServerAddr = "http://[%s]:%s" % (httpd.server_address[0],
181  httpd.server_address[1])
182  else:
183  httpServerAddr = "http://%s:%s" % (httpd.server_address[0],
184  httpd.server_address[1])
185 
186  logging.info("Server started - at %s" % httpServerAddr)
187 
188  try:
189  httpd.serve_forever()
190  except KeyboardInterrupt:
191  pass
192 
193  httpd.server_close()
194 
195  logging.info("Server stopped")
196 
197 
198 if __name__ == '__main__':
199  httpServer()