Įkvėptas komentarų: žmonės turi tvirtą ryšį tarp Perl kaip kalbos ir CGI kaip technologijos, naudotos pirmosiomis interneto dienomis.
Tai logiška: tuo metu Perl buvo viena iš nedaugelio plačiai paplitusių scenarijų kalbų ir, žinoma, CGI scenarijus buvo patogiau rašyti Perl, o ne, pavyzdžiui, apvalkalu. Tačiau tai nereiškia, kad vienas buvo prikaltas prie kito.
Apskritai, technologija buvo savaip gera: įdiegėte žiniatinklio serverį (dažniausiai Apache), sukonfigūravote katalogą, iš kurio buvo galima vykdyti scenarijus, ir galėjote paleisti viską, ką norite.
Skirtingai nuo vėlesnių sudėtingų žiniatinklio sistemų, tai tiesiogine prasme paleido programą serveryje, lygiai taip pat, kaip paleistumėte ją per terminalą ar konsolę. Viskas, kas buvo perduota programai per HTTP, buvo gauta per STDIN, o viskas, kas programos išvestis į STDOUT, buvo išsiųsta į kliento naršyklę.
Iš esmės tai buvo įprasta neinteraktyvi konsolinė programa, grynas REST API – serveris visada paleidžia programą nuo nulio, o programa veikia su tuo, kas jai buvo duota.
Neigiamas dalykas buvo tas, kad programos paleidimas visada užtrukdavo, ypač jei ji buvo parašyta scenarijų kalba, kuriai reikalingas vertimas. Vienkartinėms užklausoms tai nebuvo svarbu, tačiau kai programa paleidžiama per 0,5 sekundės ir turite 100 užklausų per sekundę, viskas pradeda šiek tiek lėtėti.
Štai kodėl vėliau buvo pristatytas FastCGI, kur programa buvo „iš anksto paleista“ ir laukė duomenų, o galiausiai perėjome prie integruotų serverių su kelių gijų tinkle.
Dabar parodysiu, kaip tai galima naudoti šiandien:
Kaip jau minėjau ankstesniame straipsnyje, sukūriau „universalios prieigos bloką“: vienos plokštės kompiuterį su maršruto parinkimo tarpiniu serveriu, kuris atskiria avis nuo ožkų, muses nuo kotletų ir ligonius nuo sveikųjų.
Tačiau, kaip ir bet kurią programą, visas šias funkcijas gali reikėti paleisti iš naujo. Negana to, namų internetas iš prigimties yra nestabilus – ant laidų užvirsta medis, ekskavatorius iškasa kažką ne taip ir staiga nustoja krauti kačių nuotraukos. Turite suprasti, kas atsitiko.
Žinoma, visada galite įvesti SSH, įvesti kelias komandas konsolėje ir sužinoti, bet ar galime tai padaryti lengviau? Rašyti atskirą žiniatinklio sistemą, kad būtų galima viską patikrinti, atrodo per daug.
Taigi, užduotis:
- Fone veikia kelios programos.
- Yra būdų, kaip patikrinti, ar programa veikia, ar „pakabinta“.
- Turime sukurti paprastą tinklalapį su dabartine būsena ir galimybe iš naujo paleisti programas.
- Kadangi tai yra giliai vietiniame tinkle, leidimo nereikia, tačiau niekas neturėtų nieko sulaužyti gremėzdiškomis rankomis.
Šiame serveryje įdiegiame „Apache“ („Nginx“ negali susidoroti su paprastu CGI be šokinėjimo).
apt install apache2
Po įdiegimo viskas veikia „iš dėžutės“, tačiau turime įjungti CGI modulį, kuris pagal numatytuosius nustatymus yra išjungtas:
cd /etc/apache2/mods-enabled
ln -s ../mods-available/cgi.load .
/etc/init.d/apache2 restart
Atlikta. Pagal numatytuosius nustatymus CGI scenarijai turėtų būti /usr/lib/cgi-bin/
ir pasiekiama per žiniatinklį kaip /cgi-bin/*.cgi
. Žinoma, tai galima pakeisti, ypač jei jus vargina pasenęs „cgi-bin“, bet dabar to nepamatysime. Ir, žinoma, šis katalogas iš pradžių tuščias.
Sukuriame pirmąjį failą, env.cgi
:
#!/bin/sh
echo "Content-Type: text/html"
echo ""
echo "<pre>"
env
echo "</pre>"
id
chmod 755 env.cgi
Prie serverio pasiekiame per naršyklę:
http://xx.xx.xx.xx/cgi-bin/env.cgi
Išvestis:
GATEWAY_INTERFACE=CGI/1.1
REMOTE_ADDR=XX.XX.XX.XX
QUERY_STRING=
HTTP_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
DOCUMENT_ROOT=/var/www/html
REMOTE_PORT=40472
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
SERVER_SIGNATURE=
Apache/2.4.62 (Debian) Server at 10.1.0.4 Port 80
....
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Jokių sudėtingų programų, naujų ar senų kalbų, struktūrų, nieko – tik paprastas apvalkalo scenarijus.
Čia yra įdomios eilutės QUERY_STRING=
ir apatinė eilutė uid=33(www-data) gid=33(www-data) groups=33(www-data)
.
The QUERY_STRING=
yra eilutė, kuri bus įvesta adresu po scenarijaus pavadinimo:
http://XX.XX.XX.XX/cgi-bin/env.cgi?blablabla -> QUERY_STRING=blablabla
Idealiu atveju tai turėtų būti URL užkoduoti GET parametrai, tačiau praktiškai nesvarbu, kokia eilutė yra, jei ji nepažeidžia simbolių konvencijų. Tokiu atveju galime nusiųsti vieną eilutę, nurodant, su kokia programa mūsų scenarijus turėtų veikti.
Apatinė eilutė, rezultatas id
komanda rodo, kad žiniatinklio serveris veikia pagal www-data
naudotojas.
Norėdami patikrinti internetą ir įvairias tarpinio serverio parinktis, galime naudoti standartą curl
:
Tiesioginė užklausa suteiks mums išorinį IP:
curl http://v4v6.ipv6-test.com/api/myip.php --silent
Užklausa per SOCKS5 tarpinį serverį suteiks mums išorinį IP, matomą per tarpinį serverį, ir taip pat patikrins tarpinio serverio funkcionalumą:
curl -x socks5h://127.0.0.1:1080 http://v4v6.ipv6-test.com/api/myip.php --silent
Kiti panašūs.
Galėtume atlikti visas patikras vienu scenarijumi ir grąžinti rezultatus JSON formatu, tačiau kai kurios užklausos gali užtrukti ilgai, o scenarijus jų visų lauktų. Tai nėra labai patogu, todėl scenarijus bus vienas, bet ką jis patikrins, priklausys nuo užklausos parametro.
Taigi, mes gauname scenarijų check_one.cgi
:
#!/bin/sh
q=$QUERY_STRING
# remove everything else
unset $(env | cut -d= -f1)
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
# mandatory header
echo "Content-Type: application/json"
echo ""
# add "x" to the parameter to avoid empty strings
if ( "x$q" = "xxray" ) ; then
# here we request the server's response, it will give us the IP address
INETIP=$( curl http://v4v6.ipv6-test.com/api/myip.php --silent )
PROXYIP=$( curl -x socks5h://127.0.0.1:1080 http://v4v6.ipv6-test.com/api/myip.php --silent )
echo "{\"extip\":\"${INETIP}\",\"proxyip\":\"${PROXYIP}\"}"
elif ( "x$q" = "xi2p" ) ; then
X=1
# here and below - the fact of the response is important, so only headers and error code
curl -x socks5h://127.0.0.1:4447 http://flibusta.i2p -I --silent -o /dev/null
if ( $? -ne 0 ) ; then
X=0
fi
echo "{\"i2p\":${X}}";
elif ( "x$q" = "xnodpi" ) ; then
X=1
curl -x socks5h://127.0.0.1:1081 https://jnn-pa.googleapis.com -I --silent -o /dev/null
if ( $? -ne 0 ) ; then
X=0
fi
echo "{\"nodpi\":${X}}";
elif ( "x$q" = "xproxy" ) ; then
X=1
curl -x socks5h://127.0.0.1:6007 http://v4v6.ipv6-test.com/api/myip.php -I --silent -o /dev/null
if ( $? -ne 0 ) ; then
X=0
fi
echo "{\"proxy\":${X}}";
else
# to not forget what options are available
echo "{\"options\":(\"proxy\",\"xray\",\"i2p\",\"nodpi\")}"
fi
Dabar, nurodę tam tikras parinktis, galime gauti atsakymą, ar konkretus maršrutas veikia, ar ne.
Paslaugas iš naujo paleisti galima taip: Kadangi žiniatinklio serveris veikia pagal www-data
vartotojas, jis neturi teisių iš naujo paleisti visko, ko norime, ir mes jai tų teisių nesuteiksime. Vietoj to, mes vykdysime paslaugas specialiai pagal ją ir taip, kad jos pačios paleistųsi iš naujo. Mes kuriame tipinius scenarijus, tokius kaip:
#!/bin/sh
#
exec > /dev/null
exec 2>&1
cd /tmp
while ( 1 ) ; do
/usr/local/etc/xray/xray -c /usr/local/etc/xray/config.json
done
The xray
programa nepaleidžiama fone, taigi, jei procesas užmuštas arba miršta savaime, begalinis scenarijaus ciklas jį paleis iš naujo. Taip apsisaugome nuo netikėtų gedimų, o pakibimo atveju užtenka užmušti pakibimo procesą – tam užtenka teisių.
Tas pats pasakytina ir apie kitus procesus.
Dabar turime pradėti procesus. Tam patogu naudoti /etc/rc.local
scenarijus (šiuo atveju jis veikia; jei ne, pridėkite jį kur nors kitur, pvz /etc/init.d
):
su www-data -s /bin/sh -c 'setsid /usr/local/bin/start_xray &'
The -s /bin/sh
parametras reikalingas, nes www-data
vartotojas neturi savo apvalkalo (žr vipw
). The -c
parametras paleidžia komandą, šiuo atveju setsid
kuris paleidžia paleisties scenarijų (šiek tiek perteklinį) kaip demoną.
Kiti panašūs. Iš naujo paleidus kompiuterį, paleidžiami paleidimo scenarijai, kurie paleidžia programas, kurios dabar veikia pagal vartotoją, o joms sugedus ar žuvus, jos automatiškai paleidžiamos iš naujo.
Vienintelis niuansas su i2pd
yra tai, kad jis ieško konfigūracijų pagrindiniame kataloge ~/.i2pd
o šiam vartotojui tai yra /var/www
katalogas (dar kartą žr vipw
), todėl nustatymai turėtų būti ten ir turėtų būti rašymo teisės į konfigūracijos katalogą.
Dabar rašome žudiko scenarijų:
#!/bin/sh
q=$QUERY_STRING
# remove everything else
unset $(env | cut -d= -f1)
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
echo "Content-Type: text/html"
echo ""
pid=
if ( "x$q" = "xxray" ) ; then
pid=$(ps ax| grep -v grep | grep 'xray/xray' | awk '{ print $1 }')
if ( -n "$pid" ) ; then
echo $pid
kill $pid
sleep 3
fi
elif ( "x$q" = "xi2p" ) ; then
pid=$(ps ax| grep -v grep | grep '/i2pd' | awk '{ print $1 }')
if ( -n "$pid" ) ; then
echo $pid
kill $pid
sleep 3
fi
elif ( "x$q" = "xnodpi" ) ; then
pid=$(ps ax| grep -v grep | grep '/ciadpi' | awk '{ print $1 }')
if ( -n "$pid" ) ; then
echo $pid
kill $pid
sleep 3
fi
else
echo "{\"options\":(\"xray\",\"i2p\",\"nodpi\")}"
fi
ps ax
pateikia procesų sąrašą, pirmasis grep
pašalina grep
pati iš išvesties, antra grep
ieško norimos programos, awk
ištraukia PID, kill
užmuša jį ir sleep
šiek tiek laukia, kol procesas baigsis.
Apskritai tai beveik viskas. Dabar sukurkime puslapį:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>HTML gate</title>
<style type="text/css">
body, html {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
height: 100%;
}
.placeholder {
display:inline-block;
width:10em;
}
.txt_error {
color:#ff0000;
font-weight:bold;
}
.txt_ok {
color:#00ff00;
font-weight:bold;
}
.hero {
height: 100vh;
background-image: url('bg-image.jpeg');
background-color: black;
background-position: center;
background-repeat: no-repeat;
background-size:cover;
color: white;
box-sizing: border-box;
}
#status {
padding:2em;
}
#buttons {
display:flex;
align-items: center;
justify-content: center;
gap: 5em;
padding-top: 300px;
}
#buttons button, #check {
padding: 15px 35px;
font-size: 20px;
border: solid 3px #ff8b00;
background: #00003370;
color: #ffc800;
border-radius: 10px;
cursor:pointer;
}
.disabled {
color:#736e5f!important;
border: solid 3px #736e5f!important;
cursor:not-allowed!important;
}
</style>
</head>
<body>
<div class="hero" id="head">
<div id="status">
<table>
<tr>
<td>External IP</td>
<td><span id="extip" class="placeholder"><span class="loading">...</span></span></td>
</tr>
<tr>
<td>Proxy IP</td>
<td><span id="proxyip" class="placeholder"><span class="loading">...</span></span></td>
</tr>
<tr>
<td>I2P network</td>
<td><span id="i2p" class="placeholder"><span class="loading">...</span></span></td>
</tr>
<tr>
<td>Proxy</td>
<td><span id="proxy" class="placeholder"><span class="loading">...</span></span></td>
</tr>
<tr>
<td>NoDPI</td>
<td><span id="nodpi" class="placeholder"><span class="loading">...</span></span></td>
</tr>
</table>
<button onclick="check()" id="check">Check</button>
</div>
<div id="buttons">
<button onclick="kill(this,'xray')">Restart Xray</button>
<button onclick="kill(this,'i2p')">Restart i2p</button>
<button onclick="kill(this,'nodpi')">Restart NoDPI</button>
</div>
</div>
<script>
function kill(ctl,id){
console.log(ctl, id);
ctl.disabled=true;
ctl.classList.add('disabled');
fetch('/cgi-bin/kill_one.cgi?'+id)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
window.setTimeout(check,10000);
ctl.disabled=false;
ctl.classList.remove('disabled');
check();
})
.catch(error => {
console.error('Error fetching data:', error);
ctl.disabled=false;
ctl.classList.remove('disabled');
});
}
function check(){
const urls = (
'xray',
'i2p',
'proxy',
'nodpi'
);
const labels = document.querySelectorAll('.placeholder');
labels.forEach(item => {
item.innerHTML = '<span class="loading">...</span>';
});
urls.forEach(url => {
fetch('/cgi-bin/check_one.cgi?'+url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
Object.entries(data).forEach(((key, value)) => {
const element = document.getElementById(key);
if (element) {
if(value == 1){
element.classList.remove('txt_error');
element.classList.add('txt_ok');
element.textContent="OK";
}else if(value == 0){
element.classList.add('txt_error');
element.classList.remove('txt_ok');
element.textContent="ERROR";
}else{
element.classList.remove('txt_error');
element.classList.add('txt_ok');
element.textContent = value;
}
}
});
})
.catch(error => {
console.error('Error fetching data:', error);
});
});
}
check();
</script>
</body>
</html>
Rezultatas:
tiek. Grynas administratoriaus darbas, apvalkalas ir šiek tiek
Jei tekste radote klaidą, siųskite pranešimą autoriui pažymėdami klaidą ir paspausdami Ctrl-Enter.