blob: 7284448f515bb00f45c7ef893a4402ff6b7a1385 [file] [log] [blame]
Davide Pesavento56a741f2018-02-10 16:30:59 -05001#!/usr/bin/env python3
Alexander Afanasyev9bcbc7c2014-04-06 19:37:37 -07002# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
3
4"""
Davide Pesavento56a741f2018-02-10 16:30:59 -05005Copyright (c) 2014-2018, Regents of the University of California,
Junxiao Shie2733332016-11-24 14:11:40 +00006 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.
Alexander Afanasyev9bcbc7c2014-04-06 19:37:37 -070012
13This file is part of NFD (Named Data Networking Forwarding Daemon).
14See AUTHORS.md for complete list of NFD authors and contributors.
15
16NFD is free software: you can redistribute it and/or modify it under the terms
17of the GNU General Public License as published by the Free Software Foundation,
18either version 3 of the License, or (at your option) any later version.
19
20NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
21without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
22PURPOSE. See the GNU General Public License for more details.
23
24You should have received a copy of the GNU General Public License along with
25NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
26"""
Chengyu Fanb07788a2014-03-31 12:15:36 -060027
Davide Pesavento56a741f2018-02-10 16:30:59 -050028from http.server import HTTPServer, SimpleHTTPRequestHandler
29from socketserver import ThreadingMixIn
Davide Pesaventob922c882018-02-24 23:55:57 -050030from urllib.parse import urlsplit
Davide Pesavento56a741f2018-02-10 16:30:59 -050031import argparse, ipaddress, os, socket, subprocess
Chengyu Fanb07788a2014-03-31 12:15:36 -060032
33
Davide Pesavento56a741f2018-02-10 16:30:59 -050034class NfdStatusHandler(SimpleHTTPRequestHandler):
35 """ The handler class to handle requests """
Chengyu Fanb07788a2014-03-31 12:15:36 -060036 def do_GET(self):
Davide Pesaventob922c882018-02-24 23:55:57 -050037 path = urlsplit(self.path).path
38 if path == "/":
Davide Pesavento56a741f2018-02-10 16:30:59 -050039 self.__serveReport()
Davide Pesaventob922c882018-02-24 23:55:57 -050040 elif path == "/robots.txt" and self.server.allowRobots:
Davide Pesavento56a741f2018-02-10 16:30:59 -050041 self.send_error(404)
Chengyu Fanb07788a2014-03-31 12:15:36 -060042 else:
Davide Pesavento56a741f2018-02-10 16:30:59 -050043 super().do_GET()
Chengyu Fanb07788a2014-03-31 12:15:36 -060044
Davide Pesavento56a741f2018-02-10 16:30:59 -050045 def __serveReport(self):
46 """ Obtain XML-formatted NFD status report and send it back as response body """
Junxiao Shi9137e9e2016-12-15 21:31:50 +000047 try:
Davide Pesavento56a741f2018-02-10 16:30:59 -050048 # enable universal_newlines to get output as string rather than byte sequence
49 output = subprocess.check_output(["nfdc", "status", "report", "xml"], universal_newlines=True)
50 except OSError as err:
51 super().log_message("error invoking nfdc: {}".format(err))
52 self.send_error(500)
53 except subprocess.CalledProcessError as err:
54 super().log_message("error invoking nfdc: command exited with status {}".format(err.returncode))
55 self.send_error(504, "Cannot connect to NFD (code {})".format(err.returncode))
56 else:
Junxiao Shi9137e9e2016-12-15 21:31:50 +000057 # add stylesheet processing instruction after the XML document type declaration
Davide Pesavento56a741f2018-02-10 16:30:59 -050058 # (yes, this is a ugly hack)
59 pos = output.index(">") + 1
Junxiao Shi9137e9e2016-12-15 21:31:50 +000060 xml = output[:pos]\
61 + '<?xml-stylesheet type="text/xsl" href="nfd-status.xsl"?>'\
62 + output[pos:]
Davide Pesavento56a741f2018-02-10 16:30:59 -050063 self.send_response(200)
64 self.send_header("Content-Type", "text/xml; charset=UTF-8")
65 self.end_headers()
66 self.wfile.write(xml.encode())
Chengyu Fanb07788a2014-03-31 12:15:36 -060067
Davide Pesavento56a741f2018-02-10 16:30:59 -050068 # override
69 def log_message(self, *args):
70 if self.server.verbose:
71 super().log_message(*args)
72
73
74class ThreadingHttpServer(ThreadingMixIn, HTTPServer):
Chengyu Fane25b0f02014-04-05 21:42:40 -060075 """ Handle requests using threads """
Davide Pesavento56a741f2018-02-10 16:30:59 -050076 def __init__(self, bindAddr, port, handler, allowRobots=False, verbose=False):
77 # socketserver.BaseServer defaults to AF_INET even if you provide an IPv6 address
78 # see https://bugs.python.org/issue20215 and https://bugs.python.org/issue24209
79 if bindAddr.version == 6:
Chengyu Fane25b0f02014-04-05 21:42:40 -060080 self.address_family = socket.AF_INET6
Davide Pesavento56a741f2018-02-10 16:30:59 -050081 self.allowRobots = allowRobots
Chengyu Fanb07788a2014-03-31 12:15:36 -060082 self.verbose = verbose
Davide Pesavento56a741f2018-02-10 16:30:59 -050083 super().__init__((str(bindAddr), port), handler)
Chengyu Fanb07788a2014-03-31 12:15:36 -060084
Davide Pesavento56a741f2018-02-10 16:30:59 -050085
86def main():
87 def ipAddress(arg):
88 """ Validate IP address """
Chengyu Fane25b0f02014-04-05 21:42:40 -060089 try:
Davide Pesavento56a741f2018-02-10 16:30:59 -050090 value = ipaddress.ip_address(arg)
91 except ValueError:
92 raise argparse.ArgumentTypeError("{!r} is not a valid IP address".format(arg))
93 return value
94
95 def portNumber(arg):
96 """ Validate port number """
Chengyu Fane25b0f02014-04-05 21:42:40 -060097 try:
Davide Pesavento56a741f2018-02-10 16:30:59 -050098 value = int(arg)
99 except ValueError:
100 value = -1
101 if value < 0 or value > 65535:
102 raise argparse.ArgumentTypeError("{!r} is not a valid port number".format(arg))
103 return value
Chengyu Fane25b0f02014-04-05 21:42:40 -0600104
Davide Pesavento56a741f2018-02-10 16:30:59 -0500105 parser = argparse.ArgumentParser(description="Serves NFD status page via HTTP")
106 parser.add_argument("-V", "--version", action="version", version="@VERSION@")
107 parser.add_argument("-a", "--address", default="127.0.0.1", type=ipAddress, metavar="ADDR",
108 help="bind to this IP address (default: %(default)s)")
109 parser.add_argument("-p", "--port", default=8080, type=portNumber,
110 help="bind to this port number (default: %(default)s)")
111 parser.add_argument("-f", "--workdir", default="@DATAROOTDIR@/ndn", metavar="DIR",
112 help="server's working directory (default: %(default)s)")
113 parser.add_argument("-r", "--robots", action="store_true",
114 help="allow crawlers and other HTTP bots")
115 parser.add_argument("-v", "--verbose", action="store_true",
116 help="turn on verbose logging")
117 args = parser.parse_args()
Chengyu Fanb07788a2014-03-31 12:15:36 -0600118
Davide Pesavento56a741f2018-02-10 16:30:59 -0500119 os.chdir(args.workdir)
Chengyu Fanb07788a2014-03-31 12:15:36 -0600120
Davide Pesavento56a741f2018-02-10 16:30:59 -0500121 httpd = ThreadingHttpServer(args.address, args.port, NfdStatusHandler,
122 allowRobots=args.robots, verbose=args.verbose)
Alexander Afanasyevb47d5382014-05-05 14:35:03 -0700123
Chengyu Fane25b0f02014-04-05 21:42:40 -0600124 if httpd.address_family == socket.AF_INET6:
Davide Pesavento56a741f2018-02-10 16:30:59 -0500125 url = "http://[{}]:{}"
Chengyu Fane25b0f02014-04-05 21:42:40 -0600126 else:
Davide Pesavento56a741f2018-02-10 16:30:59 -0500127 url = "http://{}:{}"
128 print("Server started at", url.format(*httpd.server_address))
Chengyu Fanb07788a2014-03-31 12:15:36 -0600129
130 try:
131 httpd.serve_forever()
132 except KeyboardInterrupt:
133 pass
Chengyu Fanb07788a2014-03-31 12:15:36 -0600134 httpd.server_close()
135
Chengyu Fanb07788a2014-03-31 12:15:36 -0600136
Davide Pesavento56a741f2018-02-10 16:30:59 -0500137if __name__ == "__main__":
138 main()