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