You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
6.2 KiB
Python

################################################################################
# Copyright (C) 2013-2014 gempa GmbH
#
# Common utility functions
#
# Author: Stephan Herrnkind
# Email: herrnkind@gempa.de
################################################################################
from __future__ import absolute_import, division, print_function
import socket
import sys
import traceback
from twisted.internet import reactor, defer
from twisted.python.failure import Failure
import seiscomp.logging
import seiscomp.core
import seiscomp.io
from seiscomp.client import Application
#-------------------------------------------------------------------------------
# Converts a unicode string to a byte string
b_str = lambda s: s.encode('utf-8', 'replace')
#-------------------------------------------------------------------------------
# Converts a byte string to a unicode string
u_str = lambda s: s.decode('utf-8', 'replace')
#-------------------------------------------------------------------------------
# Python version depended string conversion
if sys.version_info[0] < 3:
py2bstr = b_str
py2ustr = u_str
py3bstr = str
py3ustr = str
py3ustrlist = lambda l: l
else:
py2bstr = str
py2ustr = str
py3bstr = b_str
py3ustr = u_str
py3ustrlist = lambda l: [u_str(x) for x in l]
#-------------------------------------------------------------------------------
# Tests if a SC3 inventory object is restricted
def isRestricted(obj):
try:
return obj.restricted()
except ValueError:
return False
#-------------------------------------------------------------------------------
# Thread-safe write of string data using reactor main thread
def writeTS(req, data):
reactor.callFromThread(req.write, py3bstr(data))
#-------------------------------------------------------------------------------
# Thread-safe write of binary data using reactor main thread
def writeTSBin(req, data):
reactor.callFromThread(req.write, data)
#-------------------------------------------------------------------------------
# Finish requests deferred to threads
def onFinish(result, req):
seiscomp.logging.debug("finish value = %s" % str(result))
if isinstance(result, Failure):
err = result.value
if isinstance(err, defer.CancelledError):
seiscomp.logging.error("request canceled")
return
seiscomp.logging.error("%s %s" % (
result.getErrorMessage(), traceback.format_tb(result.getTracebackObject())))
else:
if result:
seiscomp.logging.debug("request successfully served")
else:
seiscomp.logging.debug("request failed")
reactor.callFromThread(req.finish)
#-------------------------------------------------------------------------------
# Handle connection errors
def onCancel(failure, req):
if failure:
seiscomp.logging.error("%s %s" % (
failure.getErrorMessage(), traceback.format_tb(failure.getTracebackObject())))
else:
seiscomp.logging.error("request canceled")
req.cancel()
#-------------------------------------------------------------------------------
# Handle premature connection reset
def onResponseFailure(_, call):
seiscomp.logging.error("response canceled")
call.cancel()
#-------------------------------------------------------------------------------
# Renders error page if the result set exceeds the configured maximum number
# objects
def accessLog(req, ro, code, length, err):
logger = Application.Instance()._accessLog # pylint: disable=W0212
if logger is None:
return
logger.log(AccessLogEntry(req, ro, code, length, err))
################################################################################
class Sink(seiscomp.io.ExportSink):
def __init__(self, request):
seiscomp.io.ExportSink.__init__(self)
self.request = request
self.written = 0
def write(self, data):
if self.request._disconnected: #pylint: disable=W0212
return -1
writeTSBin(self.request, data)
self.written += len(data)
return len(data)
################################################################################
class AccessLogEntry:
def __init__(self, req, ro, code, length, err):
# user agent
agent = req.getHeader("User-Agent")
if agent is None:
agent = ""
else:
agent = agent[:100].replace('|', ' ')
if err is None:
err = ""
service, user, accessTime, procTime = "", "", "", 0
net, sta, loc, cha = "", "", "", ""
if ro is not None:
# processing time in milliseconds
procTime = int((seiscomp.core.Time.GMT() - ro.accessTime).length() * 1000.0)
service = ro.service
if ro.userName is not None:
user = ro.userName
accessTime = str(ro.accessTime)
if ro.channel is not None:
if ro.channel.net is not None:
net = ",".join(ro.channel.net)
if ro.channel.sta is not None:
sta = ",".join(ro.channel.sta)
if ro.channel.loc is not None:
loc = ",".join(ro.channel.loc)
if ro.channel.cha is not None:
cha = ",".join(ro.channel.cha)
# The host name of the client is resolved in the __str__ method by the
# logging thread so that a long running DNS reverse lookup may not slow
# down the request
self.msgPrefix = "%s|%s|%s|" % (
service, py3ustr(req.getRequestHostname()), accessTime)
xff = req.requestHeaders.getRawHeaders("x-forwarded-for")
if xff:
self.userIP = xff[0].split(",")[0].strip()
else:
self.userIP = req.getClientIP()
self.clientIP = req.getClientIP()
self.msgSuffix = "|%s|%i|%i|%s|%s|%i|%s|%s|%s|%s|%s||" % (
self.clientIP, length, procTime, err, agent, code, user, net, sta, loc, cha)
def __str__(self):
try:
userHost = socket.gethostbyaddr(self.userIP)[0]
except socket.herror:
userHost = self.userIP
return self.msgPrefix + userHost + self.msgSuffix
# vim: ts=4 et