Sprawdzanie odpowiedzi serwerów HTTP w Pythonie.
Oto prosta klasa napisana w Pythonie do sprawdzania odpowiedzi serwerów HTTP:
#-*- coding: utf-8
__author__ = 'Łukasz Fidosz'
__copyright__ = 'Copyright (c) 2008 Łukasz Fidosz'
__date__ = '20.09.2008'
__license__ = 'GPL'
__version__ = '0.1.1'
from httplib import HTTPConnection
import socket
from subprocess import Popen, PIPE, STDOUT
class HTTPChecker:
def __init__(self, urllist):
self.hosts=urllist
def ping(self, host):
rstatus=' '
adress=host.split(':')[0]
p=Popen('ping -q -c3 '+adress, shell=True, stderr=STDOUT, stdout=PIPE, close_fds=True)
output=p.stdout.read()
if output.find('unknown host') != -1:
return rstatus,'Unknown host'
else:
lost_packets=int(output.split(' ')[-9][:-1])
if lost_packets > 35:
return rstatus,'Connection problems'
else:
return rstatus,'No HTTP server'
def check_host(self, host, method='HEAD'):
header={'User-agent' : 'Mozilla/5.0'}
connection=HTTPConnection(host)
try:
connection.request(method, '/', {}, header)
except socket.error:
return self.ping(host)
else:
response=connection.getresponse()
if response.status==501:
self=check_host(host, 'GET')
return response.status,response.reason
def run(self):
for host in self.hosts:
result=self.check_host(host)
message=host.ljust(30, '.')+': '+str(result[0])
if result[0]==200:
message+=' \033[1;32m'
else:
message+=' \033[1;31m'
message+=result[1]+'\033[0;m'
print message
Postaram się omówić w miarę dokładnie cały kod. Zacznijmy od metody check_host, gdyż to ona wykonuje całe zadanie, czyli pobiera od podanego w argumencie host serwera HTTP nagłówki HTTP i zwraca je.
- header={'User-agent' : 'Mozilla/5.0'} ustala nagłówki jakie będą wysyłane przy połączeniu, w naszym przypadku jest to nazwa przeglądarki jaką udawał będzie nasz skrypt, ponieważ niektóre serwisy wysyłają nagłówek 403 Forbidden gdy przy połączeniu nie otrzymają nazwy przeglądarki(robi tak na przykład serwis Distrowatch)
- connection=HTTPConnection(host) nawiązuje połączenie z danym serwerem, i przypisuje uchwyt do niego do zmiennej connection
- connection.request('HEAD', '/', {}, header) próbuje wysłać do serwera żądanie nagłówka HTTP, jak widzimy przekazujemy przy tym żądaniu wcześniej spreparowany nagłówek, jeśli operacja ta się nie powiedzie wywołujemy metodę ping, która sprawdza czy w ogóle taki host istnieje oraz czy jesteśmy połączeni z internetem i zwracamy wynik jej działania. Metodę ping omówimy za chwile.
- response=connection.getresponse() przypisuje zmiennej response obiekt zawierający odpowiedź serwera
- return response.status,response.reason zwraca jako wynik działania metody krotkę zawierającą kod odpowiedzi serwera oraz jego opis
Metoda run wywołuje funkcję check_host dla każdej nazwy hosta lub adresu IP podanych do klasy w metodzie __init__, po czym drukuje odpowiednio sformatowany wynik. Omówmy jej kod:
- result=self.check_host(host) przypisuje to zmiennej result krotkę zwróconą przez metodę check_host
- message=host.ljust(30, '.')+': '+str(result[0]) przypisuje do zmiennej message wartość zmiennej host dopełnioną, tak dla czytelności z prawej strony kropkami do 30 znaków, po czym dodaje do tak przygotowanego stringa kod odpowiedzi HTTP
- Następnie sprawdzamy czy opis odpowiedzi HTTP to OK, jeśli tak jest zmieniamy jego kolor na zielony instrukcją message+=' \033[1;32m', jeśli odpowiedź jest inna dajemy jej kolor czerwony poprzez dopisanie do zmiennej message wartości ' \033[1;31m'
- message+=result[1]+'\033[0;m' przypisuje do zmiennej message wspomniany już opis odpowiedzi HTTP, po czym przywraca domyślny kolor napisów
Została nam jeszcze metoda ping, jej zadanie zostało już w skrócie opisane, przejdźmy więc do jej omawiania.
- rstatus=' ' ustala co będzie zwracane przez metodę ping jako kod odpowiedzi, jako że nie mamy tu do czynienia z kodami HTTP a chodzi nam jedynie o zwrócenie opisu, dajemy tutaj ze względów estetycznych 3 spacje(bo tyle cyfr mają kody odpowiedzi HTTP)
- p=Popen('ping -q -c3 '+host, shell=True, stderr=STDOUT, stdout=PIPE, close_fds=True) wykonuje polecenie systemowe ping po czym przypisuje uchwyt do niego do zmiennej p. Popen z takimi parametrami jest metodą zastępującą nam metodę popem4 z modułu os, której używanie jest już niezalecane, funkcjonalność metody popem4 uzyskujemy sprawiając ze wyjście błędów jest przekazywane na standardowe wyjście wywoływanego polecenia(stderr=STDOUT)
- output=p.stdout.read() przypisujemy tutaj do zmiennej output wszystko co zwróciło polecenie systemowe ping, robimy to wywołując metodę read() na p.stdout, który jest obiektem pliko podobnym
- if output.find('unknown host') != -1: sprawdza czy ping nie zwrócił przypadkiem wyrażenia unknown host, jeśli tak kończymy wywołanie funkcji zwracając jako opis błędu string Unknown host. W przeciwnym przypadku za pomocą instrukcji lost_packets=int(output.split(' ')[-9][:-1]) dzielimy nasz string w miejscu spacji zawierający wyjście na oddzielne elementy, po czym z tak otrzymanej listy wypieramy dziewiąty od końca element, on zawiera informacje o procencie utraconych pakietów, wyrażenie [:-1] usuwa znak %, który nie jest nam potrzebny. Sprawdzamy teraz czy ilość utraconych pakietów nie jest przypadkiem większa niż 35%(wysłaliśmy 3 pakiety i pozwalamy się ewentualnie jednemu zagubić) zwracamy jako opis błędu komunikat Connection problems, jeśli jednak host daje się fingować oznacza to ze serwer HTTP nie jest na nim uruchomiony, o czym również informujemy.
Przykład użycia
from httpchecker import *
check=HTTPChecker(('www.google.pl', 'www.archlinux.org', 'distrowatch.com'))
check.run()
Co daje nam na przykład taki wynik:
www.google.pl.................: 200 OK
www.archlinux.org.............: 200 OK
distrowatch.com...............: 200 OK
20 września 2008 o 14:07:51
Jako host można podać host:port jak np '127.0.0.1:8080' i ładnie pokazało mi OK :)
Jednak jak wyłączyłem serwer to dostałem komunikat "Unknown host", bo nie wyodrębniło IP.
Przykładowo mój router ma bardzo prosty serwer HTTP, który rozumie tylko GET i POST a na HEAD odpowiada kodem 501 czyli Not Implemented. Można więc dla takich przypadków przy kodzie 501 powtórzyć zapytanie ale z GET zamiast HEAD.
Dalej mój router zwrócił kod 200 (jak mu wysłałem GET) ale komunikat był 'Ok' a nie 'OK' co chyba jest w porządku z rfc. To te numerki są dla nieludzi więc porównywanie do 'OK' należałoby zastąpić sprawdzaniem kodu.
Skrypt brzydko zakłada, że wyjście to terminal, który obsłuży \033[1;32m itp - wie ktoś jak powinno się obchodzić z kolorami jak najbardziej uniwersalnie?
btw. w czasie zabawy skryptem serwer www.archlinux.org zwrócił mi 500 ale to chyba nie prze mnie ;)
20 września 2008 o 18:52:54
\033[1m to nie jest przywracanie poprzedniego koloru, to ustawianie rozjasnienia... \033[0m to reset koloru... \033[1;32m analogicznie to *jasno*zielony... a, i to dziala tylko na terminalach zgodnych ansi, czyli np. XPkowy cmd.exe tego nie obsluzy...
20 września 2008 o 19:09:34
@Taeril-dzięki za dokładniejsze przetestowanie skryptu i uwagi:) Wszystko już poprawiłem, co do nazwy chosta poprostu zawsze odrzucamy w metodzie ping to co jest po dwukropku bez względu na to czy cos tam jest czy nie, a jeśli chodzi o 501 to dodałem argument jakim jest typ żądania, domyślnie 'HEAD', a gdy trafimy na 501 metoda wykonuje jeszcze raz samą siebie z typem żądania ustawionym na 'GET'. Co do kolorow, poszukam-może znajde jakieś ciekawe rozwiązanie:)
20 września 2008 o 19:10:27
@joru-dzieki:) pomyliło mi się;) już poprawione:)
20 września 2008 o 20:25:40
message+=' \033[1;30m'
message+=result[1]+'\033[1;m'
poprawione, ale zle :p
\033[1;30m to jasnoczarny, a powinno byc \033[1;31m - jasnoczerwony...
a nizej \033[0m do resetu...
20 września 2008 o 20:36:54
Hehe-ale mi sie kreca te kolorki;) Teraz ok?:)
20 września 2008 o 21:18:56
znanazlem cos ciekawego o kolorowaniu:
http://code.activestate.com/recipes/475116/ :)