blob: 52acae6a8c1401396bf5b7471775b3018aab37eb [file] [log] [blame]
Chengyu Fanb07788a2014-03-31 12:15:36 -06001#!/usr/bin/env python2.7
Alexander Afanasyev9bcbc7c2014-04-06 19:37:37 -07002# -*- Mode: python; py-indent-offset: 4; indent-tabs-mode: nil; coding: utf-8; -*-
3
4"""
5Copyright (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
12This file is part of NFD (Named Data Networking Forwarding Daemon).
13See AUTHORS.md for complete list of NFD authors and contributors.
14
15NFD is free software: you can redistribute it and/or modify it under the terms
16of the GNU General Public License as published by the Free Software Foundation,
17either version 3 of the License, or (at your option) any later version.
18
19NFD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
20without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
21PURPOSE. See the GNU General Public License for more details.
22
23You should have received a copy of the GNU General Public License along with
24NFD, e.g., in COPYING.md file. If not, see <http://www.gnu.org/licenses/>.
25"""
Chengyu Fanb07788a2014-03-31 12:15:36 -060026
27from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
28from SocketServer import ThreadingMixIn
29import sys
30import commands
31import StringIO
32import urlparse
33import logging
34import cgi
35import argparse
Chengyu Fane25b0f02014-04-05 21:42:40 -060036import socket
Chengyu Fanb07788a2014-03-31 12:15:36 -060037
38
39class StatusHandler(BaseHTTPRequestHandler):
40 """ The handler class to handle requests."""
41 def do_GET(self):
42 # get the url info to decide how to respond
43 parsedPath = urlparse.urlparse(self.path)
44 resultMessage = ""
45 if parsedPath.path == "/robots.txt":
46 if self.server.robots == False:
47 # return User-agent: * Disallow: / to disallow robots
48 resultMessage = "User-agent: * \nDisallow: /\n"
49 self.send_response(200)
50 self.send_header("Content-type", "text/plain")
51 elif parsedPath.path == "/":
52 # get current nfd status, and use it as result message
53 resultMessage = self.getNfdStatus()
54 self.send_response(200)
55 self.send_header("Content-type", "text/html; charset=UTF-8")
56 else:
57 # non-existing content
58 resultMessage = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 '\
59 + 'Transitional//EN" '\
60 + '"http://www.w3.org/TR/html4/loose.dtd">\n'\
61 + '<html><head><title>Object not '\
62 + 'found!</title><meta http-equiv="Content-type" ' \
63 + 'content="text/html;charset=UTF-8"></head>\n'\
64 + '<body><h1>Object not found!</h1>'\
65 + '<p>The requested URL was not found on this server.</p>'\
66 + '<h2>Error 404</h2>'\
67 + '</body></html>'
68 self.send_response(404)
69 self.send_header("Content-type", "text/html; charset=UTF-8")
70
71 self.end_headers()
72 self.wfile.write(resultMessage)
73
Chengyu Fanb07788a2014-03-31 12:15:36 -060074 def log_message(self, format, *args):
75 if self.server.verbose:
Chengyu Fane25b0f02014-04-05 21:42:40 -060076 logging.info("%s - %s\n" % (self.address_string(),
77 format % args))
Chengyu Fanb07788a2014-03-31 12:15:36 -060078
79 def getNfdStatus(self):
80 """
81 This function tries to call nfd-status command
82 to get nfd's current status, after convert it
83 to html format, return it to the caller
84 """
85 (res, output) = commands.getstatusoutput('nfd-status')
86
87 htmlStr = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional'\
88 + '//EN" "http://www.w3.org/TR/html4/loose.dtd">\n'\
89 + '<html><head><title>NFD status</title>'\
90 + '<meta http-equiv="Content-type" content="text/html;'\
91 + 'charset=UTF-8"></head>\n<body>'
92 if res == 0:
93 # parse the output
94 buf = StringIO.StringIO(output)
95 firstLine = 0
96 for line in buf:
97 if line.endswith(":\n"):
98 if firstLine != 0:
99 htmlStr += "</ul>\n"
100 firstLine += 1
101 htmlStr += "<b>" + cgi.escape(line.strip()) + "</b><br>\n"\
102 + "<ul>\n"
103 continue
104 line = line.strip()
105 htmlStr += "<li>" + cgi.escape(line) + "</li>\n"
106 buf.close()
107 htmlStr += "</ul>\n"
108 else:
109 # return connection error code
110 htmlStr = htmlStr + "<p>Cannot connect to NFD,"\
111 + " Code = " + str(res) + "</p>\n"
112 htmlStr = htmlStr + "</body></html>"
113 return htmlStr
114
115
Chengyu Fane25b0f02014-04-05 21:42:40 -0600116class ThreadHttpServer(ThreadingMixIn, HTTPServer):
117 """ Handle requests using threads """
Chengyu Fanb07788a2014-03-31 12:15:36 -0600118 def __init__(self, server, handler, verbose=False, robots=False):
Chengyu Fane25b0f02014-04-05 21:42:40 -0600119 serverAddr = server[0]
120 # socket.AF_UNSPEC is not supported, check whether it is v6 or v4
121 ipType = self.getIpType(serverAddr)
122 if ipType == socket.AF_INET6:
123 self.address_family = socket.AF_INET6
124 elif ipType == socket.AF_INET:
125 self.address_family == socket.AF_INET
126 else:
127 logging.error("The input IP address is neither IPv6 nor IPv4")
128 sys.exit(2)
129
Chengyu Fanb07788a2014-03-31 12:15:36 -0600130 try:
131 HTTPServer.__init__(self, server, handler)
132 except Exception as e:
133 logging.error(str(e))
134 sys.exit(2)
135 self.verbose = verbose
136 self.robots = robots
137
Chengyu Fane25b0f02014-04-05 21:42:40 -0600138 def getIpType(self, ipAddr):
139 """ Get ipAddr's address type """
140 # if ipAddr is an IPv6 addr, return AF_INET6
141 try:
142 socket.inet_pton(socket.AF_INET6, ipAddr)
143 return socket.AF_INET6
144 except socket.error:
145 pass
146 # if ipAddr is an IPv4 addr return AF_INET, if not, return None
147 try:
148 socket.inet_pton(socket.AF_INET, ipAddr)
149 return socket.AF_INET
150 except socket.error:
151 return None
152
Chengyu Fanb07788a2014-03-31 12:15:36 -0600153
154# main function to start
155def httpServer():
156 parser = argparse.ArgumentParser()
157 parser.add_argument("-p", type=int, metavar="port number",
158 help="Specify the HTTP server port number, default is 8080.",
159 dest="port", default=8080)
Chengyu Fane25b0f02014-04-05 21:42:40 -0600160 # if address is not specified, use 127.0.0.1
161 parser.add_argument("-a", default="127.0.0.1", metavar="IP address", dest="addr",
Chengyu Fanb07788a2014-03-31 12:15:36 -0600162 help="Specify the HTTP server IP address.")
163 parser.add_argument("-r", default=False, dest="robots", action="store_true",
164 help="Enable HTTP robots to crawl; disabled by default.")
165 parser.add_argument("-v", default=False, dest="verbose", action="store_true",
166 help="Verbose mode.")
167
168 args = vars(parser.parse_args())
169 localPort = args["port"]
170 localAddr = args["addr"]
171 verbose = args["verbose"]
172 robots = args["robots"]
173
174 # setting log message format
175 logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s',
176 level=logging.INFO)
177
Chengyu Fane25b0f02014-04-05 21:42:40 -0600178 # if port is invalid, exit
Chengyu Fanb07788a2014-03-31 12:15:36 -0600179 if localPort <= 0 or localPort > 65535:
180 logging.error("Specified port number is invalid")
181 sys.exit(2)
182
Chengyu Fane25b0f02014-04-05 21:42:40 -0600183 httpd = ThreadHttpServer((localAddr, localPort), StatusHandler,
Chengyu Fanb07788a2014-03-31 12:15:36 -0600184 verbose, robots)
Chengyu Fane25b0f02014-04-05 21:42:40 -0600185 httpServerAddr = ""
186 if httpd.address_family == socket.AF_INET6:
187 httpServerAddr = "http://[%s]:%s" % (httpd.server_address[0],
188 httpd.server_address[1])
189 else:
190 httpServerAddr = "http://%s:%s" % (httpd.server_address[0],
191 httpd.server_address[1])
Chengyu Fanb07788a2014-03-31 12:15:36 -0600192
Chengyu Fane25b0f02014-04-05 21:42:40 -0600193 logging.info("Server started - at %s" % httpServerAddr)
Chengyu Fanb07788a2014-03-31 12:15:36 -0600194
195 try:
196 httpd.serve_forever()
197 except KeyboardInterrupt:
198 pass
199
Chengyu Fanb07788a2014-03-31 12:15:36 -0600200 httpd.server_close()
201
Chengyu Fane25b0f02014-04-05 21:42:40 -0600202 logging.info("Server stopped")
Chengyu Fanb07788a2014-03-31 12:15:36 -0600203
204
205if __name__ == '__main__':
206 httpServer()