#!/usr/bin/env python
# Copyright (C) 2010, Adam Fourney <afourney@cs.uwaterloo.ca>
#
# This file is part of Adaptable GIMP
#
# Adaptable GIMP is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import sys, subprocess, socket, os, errno, random, hashlib, time, re
if sys.platform == 'win32':
from threading import Thread
import array
class Value:
def __init__(self, typecode, arg):
self.__arr = array.array(typecode, [arg])
def getv(self):
return self.__arr[0]
def setv(self, val):
self.__arr[0] = val
value = property(getv, setv)
else:
from multiprocessing import Process, Value
# CONSTANTS
PROXY_TO = 'www.uwaterloo.ca'
HOST = '127.0.0.1'
PREFERRED_PORT = 8080
HEARTBEAT_PERIOD = 15
APPLICATION = './gimp-2.6.exe' if sys.platform == 'win32' else './gimp-2.6'
#####################################################################
def main():
thread_q = []
shutdown = Value('i', 0)
is_online = Value('i', 1)
# Set up the socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Try finding a free port to listen on.
# Stop after 10 tries.
tries = 10
port = PREFERRED_PORT
while (tries > 0):
tries = tries - 1
try:
s.bind((HOST, port))
s.listen(5)
print "Listening on port {0}".format(port)
break
except socket.error, (err, msg):
if (tries == 0):
raise
elif (err == errno.EADDRINUSE):
port = random.randint(1024, 49151)
continue
else:
raise
# Set socket timeout
s.settimeout(0.5)
# Spawn the heartbeat
heartbeat_thread = spawn_best_thread(target=start_heartbeat, args=(shutdown,is_online))
heartbeat_thread.start()
# Spawn our process
app_thread = spawn_best_thread(target=spawn_app, args=(shutdown,port))
app_thread.start()
# Handle incoming connections
while shutdown.value == 0:
# Poll the children and reap zombies
new_q = []
for p in thread_q:
if p.is_alive():
new_q.append(p)
thread_q = new_q
# Accept a new connection
try:
conn, addr = s.accept()
except socket.timeout:
continue
except socket.error, err:
if (err == errno.EINTR):
continue
else:
raise
# Service the request in a new thread
conn.settimeout(None)
p = spawn_best_thread(target=service_request, args=(conn,is_online))
thread_q.append(p)
p.start()
s.close()
#####################################################################
def spawn_best_thread(target, args):
if sys.platform == 'win32':
return Thread(target=target, args=args)
else:
return Process(target=target, args=args)
#####################################################################
def start_heartbeat(shutdown, is_online):
sleep_for = 0
while shutdown.value == 0:
# Sleep for half a second at a time to allow for checking of the
# shutdown condition.
if (sleep_for > 0):
time.sleep(0.5)
sleep_for = sleep_for - 0.5
continue
# Do actual work
start_time = time.clock()
previous_status = is_online.value
new_status = previous_status
try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(HEARTBEAT_PERIOD)
client.connect((PROXY_TO, 80))
client.sendall("HEAD / HTTP/1.1\r\nHost: {0}\r\nConnection: close\r\n\r\n".format(PROXY_TO))
response = ""
while 1:
data = client.recv(1024)
if not data:
break
response += data
if re.search('^HTTP\/\d\.\d\s+200\s+OK', response):
new_status = 1
else:
new_status = 0
except socket.error:
new_status = 0
except socket.timeout:
new_status = 0
# Shutdown and close the connection, but report no errors
try:
client.shutdown(socket.SHUT_RDWR);
except socket.error:
pass
try:
client.close()
except socket.error:
pass
if new_status != previous_status:
print "Connection status changed. Now {0}".format('Online' if new_status else 'Offline')
is_online.value = new_status
# Arrange to sleep a little
sleep_for = HEARTBEAT_PERIOD - (time.clock() - start_time)
#####################################################################
def service_request(conn, is_online):
# Read the request, respond and exit.
request= ""
while 1:
data = conn.recv(1024)
if not data:
break
request += data
# Requests are terminated by the following sequence
pos = request.find("\r\n\r\n")
if (pos > -1):
data = data[0:pos+4]
break
response = make_request(request, is_online)
conn.sendall(response)
try:
conn.shutdown(socket.SHUT_RDWR);
except socket.error:
pass
conn.close()
#####################################################################
def make_request(data, is_online):
# Split the request into lines
lines = data.split("\r\n")
if data.endswith("\r\n"):
lines.pop()
# Hash the first line of the request for use as a key
first_line = lines[0];
key = hashlib.md5(first_line).hexdigest()
# Check for special PROXY messages
if first_line == "PROXY GET STATUS":
status_str = "Online" if is_online.value > 0 else "Offline"
return "Status: {0}\r\n\r\n".format(status_str)
# Exit early if we are offline
if is_online.value == 0:
return read_from_cache(key)
# Modify the request for proxying
data = "";
for line in lines:
if line.startswith('Connection:'):
data = data + 'Connection: close' + "\r\n"
elif line.startswith('Host:'):
data = data + 'Host: {0}'.format(PROXY_TO) + "\r\n"
else:
data = data + line + "\r\n"
# Try to fetch from the server, but fall back on the cache if we're offline
try:
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(HEARTBEAT_PERIOD)
client.connect((PROXY_TO, 80))
client.sendall(data)
except:
return read_from_cache(key)
# Read the response
response = ""
while 1:
data = client.recv(1024)
if not data:
break
response += data
client.close()
# Cache the response and return
write_to_cache(key, response)
return response
#####################################################################
# Read a response from the cache. Return 404 if there is a problem.
def read_from_cache(key):
try:
f = open("web_cache/{0}.tmp".format(key), "r")
f_data = f.read()
f.close()
except IOError as (errnum, strerror):
if (errnum == errno.ENOENT):
response = """HTTP/1.1 404 Not Found\r
Content-Type: text/html\r
Connection: close\r
Content-Length: 78\r
\r
\r
<HTML><HEAD><TITLE>404</TITLE></HEAD><BODY>Page Not Found: 404</BODY></HTML>"""
return response
else:
raise
return f_data
#####################################################################
# Write a response to the cache. Create a web_cache directory if required.
def write_to_cache(key, response):
if not os.path.isdir('web_cache'):
os.mkdir('web_cache')
f = open('web_cache/{0}.tmp'.format(key), 'w')
f.write(response)
f.close()
#####################################################################
# Spawn the main process
def spawn_app(shutdown, port):
os.environ['AGIMP_PROXY_PORT'] = str(port)
subprocess.call([APPLICATION])
shutdown.value = 1
######## CALL MAIN ########
if __name__ == '__main__':
main()
转载于:https://my.oschina.net/u/1385797/blog/174017