- Opplæringen
God ettermiddag.
I denne artikkelen vil jeg gjerne snakke om FastCGI-protokollen og hvordan du arbeider med den. Til tross for at selve protokollen og dens implementering dukket opp tilbake i 1996, er det rett og slett ingen detaljerte manualer for denne protokollen - utviklerne skrev aldri hjelp til sitt eget bibliotek. Men for to år siden, da jeg nettopp begynte å bruke denne protokollen, hørte jeg ofte setninger som "Jeg forstår ikke helt hvordan jeg bruker dette biblioteket." Det er denne ulempen jeg vil korrigere - å skrive en detaljert veiledning for bruk av denne protokollen i et flertråds program og anbefalinger for valg ulike parametere, som kan brukes av alle.
Den gode nyheten er at metoden for å kode data i FastCGI og i CGI er den samme, bare metoden for å overføre dem endres: hvis et CGI-program bruker standard input-output-grensesnitt, så bruker et FastCGI-program sockets. Med andre ord, du trenger bare å forstå noen få funksjoner i biblioteket for å jobbe med FastCGI, og så bare bruke erfaringen med å skrive CGI-programmer, som det heldigvis er mange eksempler på.
Så i denne artikkelen skal vi se på:
- Hva er FastCGI og hvordan skiller det seg fra CGI-protokollen
- Hvorfor trenger jeg FastCGI når det allerede er mange språk for webutvikling
- Hvilke implementeringer av FastCGI-protokollen finnes?
- Hva er stikkontakter
- Beskrivelse av FastCGI-bibliotekets funksjoner
- Et enkelt eksempel på et flertråds FastCGI-program
- Enkelt Nginx-konfigurasjonseksempel
Dessverre er det veldig vanskelig å skrive en artikkel på samme måte forståelig for nybegynnere og interessant for erfarne oldtimers, så jeg vil prøve å dekke alle punktene så detaljert som mulig, og du kan ganske enkelt hoppe over de delene som ikke er interessante for deg.
Hva er FastCGI?
Du kan lese om FastCGI på Wikipedia. I et nøtteskall er det et CGI-program som kjører i en loop. Hvis et vanlig CGI-program startes på nytt for hver ny forespørsel, bruker et FastCGI-program en kø med forespørsler som behandles sekvensielt. Tenk deg nå: din 4-8 kjerneserver mottok 300-500 samtidige forespørsler. Et typisk CGI-program vil kjøre de samme 300-500 ganger. Det er åpenbart for mange prosesser - serveren din er fysisk ute av stand til å behandle dem alle på en gang. Dette betyr at du vil ende opp med en kø med prosesser som venter på prosessortidsdelen. Vanligvis vil planleggeren fordele prosessoren jevnt (så i dette tilfellet er prioriteringene til alle prosesser de samme), noe som betyr at du vil ha 300-500 "nesten klare" svar på forespørsler. Det høres ikke særlig optimistisk ut, gjør det? I FastCGI-programmet løses alle disse problemene med en enkel forespørselskø (det vil si at forespørselsmultipleksing brukes).Hvorfor trenger jeg FastCGI når jeg allerede har PHP, Ruby, Python, Perl, etc.?
Kanskje, hovedårsaken- det kompilerte programmet vil kjøre raskere enn det tolkede. For PHP, for eksempel, er det en hel rekke akseleratorer, inkludert APC, eAccelerator, XCache, som reduserer tiden for kodetolkning. Men for C/C++ er alt dette rett og slett ikke nødvendig.Den andre tingen du må huske er at dynamisk skriving og søppelsamleren tar opp mye ressurser. Noen ganger - mye. For eksempel tar heltallsarrayer i PHP omtrent 18 ganger mer minne (opptil 35 ganger avhengig av ulike PHP-kompileringsalternativer) enn i C/C++ for samme mengde data, så tenk på overheaden for relativt store datastrukturer.
For det tredje kan et FastCGI-program lagre data som er felles for forskjellige forespørsler. For eksempel, hvis PHP begynner å behandle en forespørsel fra bunnen av hver gang, så kan FastCGI-programmet gjøre en rekke forberedende handlinger selv før den første forespørselen kommer, for eksempel allokere minne, laste inn ofte brukte data, etc. - åpenbart kan alt dette forbedre den generelle ytelsen til systemet.
For det fjerde er skalerbarhet. Hvis mod_php antar at Apache-nettserveren og PHP er på samme maskin, kan FastCGI-applikasjonen bruke TCP-sockets. Med andre ord kan du ha en hel klynge av flere maskiner, kommunikasjon med som utføres over nettverket. Samtidig støtter FastCGI også Unix-domene-sockets, som lar deg effektivt kjøre en FastCGI-applikasjon og en webserver på samme maskin om nødvendig.
For det femte - sikkerhet. Tro det eller ei, med standardinnstillinger lar Apache deg gjøre alt under solen. For eksempel, hvis en angriper laster opp et ondsinnet skript exploit.php.jpg til et nettsted under dekke av et "uskyldig bilde" og deretter åpner det i nettleseren, vil Apache "ærlig" kjøre den ondsinnede PHP-koden. Kanskje den eneste ganske pålitelige løsningen er å fjerne eller endre alle potensielt farlige utvidelser fra navnene på nedlastede filer, i dette tilfellet - php, php4, php5, phtml, etc. Denne teknikken brukes for eksempel i Drupal - et understrek legges til alle "ekstra" utvidelser og resultatet er exploit.php_.jpg. Det skal imidlertid bemerkes at systemadministratoren kan legge til evt ytterligere utvidelse fil som en PHP-behandler, så noe .html kunne plutselig bli til et forferdelig sikkerhetshull bare fordi .php-en så stygg ut, var dårlig for SEO eller kunden ikke likte. Så hva gir FastCGI oss når det gjelder sikkerhet? For det første, hvis du bruker Nginx-webserveren i stedet for Apache, vil den ganske enkelt servere statiske filer. Punktum. Med andre ord vil exploit.php.jpg-filen bli servert "som den er", uten noen behandling på serversiden, så det vil rett og slett ikke være mulig å kjøre et ondsinnet skript. For det andre kan FastCGI-programmet og webserveren fungere under forskjellige brukere, noe som betyr at de vil ha forskjellige rettigheter til filer og mapper. For eksempel kan en webserver bare lese nedlastede filer - dette er nok til å returnere statiske data, og et FastCGI-program kan bare lese og endre innholdet i mappen med nedlastede filer - dette er nok til å laste ned nye og slette gamle filer, men tilgang direkte til de nedlastede filene selv vil ikke ha, noe som betyr oppfylle ondsinnet kode det vil han heller ikke kunne. For det tredje kan et FastCGI-program kjøres i en chroot som er forskjellig fra chrooten til webserveren. Chroot selv (endre rotkatalogen) lar deg begrense programrettighetene sterkt, det vil si øke generell sikkerhet systemet, fordi programmet rett og slett ikke vil kunne få tilgang til filer utenfor den angitte katalogen.
Hvilken webserver med FastCGI-støtte er bedre å velge?
Kort sagt, jeg bruker Nginx. Generelt er det ganske mange servere som støtter FastCGI, inkludert kommersielle, så la oss vurdere flere alternativer.Apache er kanskje det første du tenker på, selv om det bruker mye mer ressurser enn Nginx. For eksempel, for 10 000 inaktive HTTP Keep-alive-tilkoblinger, bruker Nginx omtrent 2,5 millioner minne, noe som er ganske realistisk selv for en relativt svak maskin, og Apache blir tvunget til å opprette en ny tråd for hver ny tilkobling, så 10 000 tråder er ganske enkelt Fantastisk.
Lighttpd - Den største ulempen med denne webserveren er at den behandler alle forespørsler i en tråd. Dette betyr at det kan være problemer med skalerbarheten – du vil rett og slett ikke kunne bruke alle 4-8 kjerner av moderne prosessorer. Og for det andre, hvis nettservertråden av en eller annen grunn fryser (for eksempel på grunn av lang ventetid på svar fra harddisken), vil hele serveren fryse. Med andre ord vil alle andre klienter slutte å motta svar på grunn av en treg forespørsel.
En annen kandidat er Cherokee. I følge utviklerne fungerer det i noen tilfeller raskere enn Nginx og Lighttpd.
Hvilke implementeringer av FastCGI-protokollen finnes?
For øyeblikket er det to implementeringer av FastCGI-protokollen - libfcgi.lib-biblioteket fra skaperne av FastCGI-protokollen, og Fastcgi++ - et C++-klassebibliotek. Libfcgi har blitt utviklet siden 1996 og er ifølge Open Market veldig stabil og mer utbredt, så vi vil bruke det i denne artikkelen. Jeg vil merke meg at biblioteket er skrevet i C, den innebygde C++ "wrapperen" kan ikke kalles høyt nivå, så vi vil bruke C-grensesnittet.Jeg tror det ikke er noen vits i å stoppe med å installere selve biblioteket - det har en makefile, så det burde ikke være noen problemer. Dessuten i populære distribusjoner dette biblioteket er tilgjengelig fra pakker.
Hva er stikkontakter?
Et generelt konsept for stikkontakter kan fås fra Wikipedia. I et nøtteskall er stikkontakter en metode for kommunikasjon mellom prosesser.Som vi husker, i alle moderne operativsystemer bruker hver prosess sitt eget adresseområde. Operativsystemkjernen er ansvarlig for direkte tilgang til RAM, og hvis et program får tilgang til en minneadresse som ikke eksisterer (i sammenheng med et gitt program), vil kjernen returnere en segmenteringsfeil og lukke programmet. Dette er fantastisk - nå kan feil i ett program rett og slett ikke skade andre - de er så å si i andre dimensjoner. Men siden programmene har ulike adresserom, kan det heller ikke være delt data eller datautveksling. Men hva om du virkelig trenger å overføre data fra ett program til et annet? Faktisk ble sockets utviklet for å løse dette problemet - to eller flere prosesser (les: programmer) kobles til samme socket og begynner å utveksle data. Det viser seg å være et slags "vindu" inn i en annen verden - gjennom det kan du motta og sende data til andre strømmer.
Avhengig av hvilken type tilkobling som brukes, er stikkontakter forskjellige. For eksempel er det TCP-stikkontakter - de bruker vanlig nettverk for datautveksling, det vil si at programmer kan kjøres på forskjellige datamaskiner. Det nest vanligste alternativet - Unix domene sockets - er egnet for å utveksle data bare innenfor én maskin og ser ut som en vanlig bane i filsystemet, men i virkeligheten HDD ikke brukt - all datautveksling skjer i RAM. På grunn av det faktum at det ikke er nødvendig å bruke nettverksstabel, er litt raskere (omtrent 10%) enn TCP-sockets. For OS Windows gitt muffevarianten kalles et navngitt rør.
Eksempler på bruk av sockets for GNU/Linux OS finner du i denne artikkelen. Hvis du ikke har jobbet med stikkontakter før, vil jeg anbefale å sette deg inn i det – det er ikke obligatorisk, men det vil forbedre forståelsen av tingene som presenteres her.
Hvordan bruker jeg Libfcgi-biblioteket?
Så vi ønsker å lage en flertråds FastCGI-applikasjon, så la meg beskrive noen av de viktigste funksjonene.Først av alt, må biblioteket initialiseres:
int FCGX_Init(void);
Merk følgende! Denne funksjonen må kalles før alle andre funksjoner i dette biblioteket og bare én gang (bare én gang, for et hvilket som helst antall tråder).
Deretter må vi åpne en lyttekontakt:
int FCGX_OpenSocket(const char *path, int backlog);
Stivariabelen inneholder socket-tilkoblingsstrengen. Både Unix domene sockets og TCP sockets støttes, alle nødvendig arbeid Biblioteket vil gjøre utarbeidelsen av parametere og funksjonskall selv.
Eksempel på tilkoblingsstrenger for Unix-domenekontakter:
"/tmp/fastcgi/mysocket" "/tmp/fcgi_example.bare.sock"
Jeg tror alt er klart her: du trenger bare å passere en unik bane som en streng, og alle prosesser som samhandler med kontakten må ha tilgang til den. Jeg gjentar nok en gang: denne metoden fungerer bare innenfor én datamaskin, men er noe raskere enn TCP-sockets.
Eksempel på tilkoblingsstrenger for TCP-sokler:
":5000"
":9000"
I dette tilfellet åpnes en TCP-socket på den angitte porten (i dette tilfellet henholdsvis 5000 eller 9000), og forespørsler vil bli akseptert fra enhver IP-adresse. Merk følgende! Denne metoden er potensielt usikker - hvis serveren din er koblet til Internett, vil FastCGI-programmet godta forespørsler fra en hvilken som helst annen datamaskin. Dette betyr at enhver angriper vil kunne sende en "dødspakke" til FastCGI-programmet ditt. Selvfølgelig er det ingenting bra med dette - i beste fall kan programmet ditt ganske enkelt "krasje" og resultere i et tjenestenekt (DoS-angrep, hvis du vil), i verste fall, ekstern kjøring av kode (hvis du er virkelig uheldig), så begrens alltid tilgangen til slike porter ved å bruke en brannmur (brannmur), og tilgang bør kun gis til de IP-adressene som faktisk brukes til vanlig arbeid FastCGI-programmer (prinsippet om "alt som ikke er eksplisitt tillatt" er forbudt).
Følgende er et eksempel på tilkoblingsstrenger:
"*:5000"
"*:9000"
Metoden er helt lik den forrige: en TCP-kontakt åpnes for å akseptere tilkoblinger fra en hvilken som helst IP-adresse, så i dette tilfellet er det også nødvendig å konfigurere brannmuren nøye. Den eneste fordelen med en slik tilkoblingslinje er rent administrativ - alle som leser konfigurasjonsfiler en programmerer eller systemadministrator vil forstå at programmet ditt godtar tilkoblinger fra hvilken som helst IP-adresse, så alt annet likt er det bedre å foretrekke dette alternativet fremfor det forrige.
Et sikrere alternativ er å spesifisere IP-adressen eksplisitt i tilkoblingsstrengen:
"5.5.5.5:5000"
"127.0.0.1:9000"
I dette tilfellet vil forespørsler bare bli akseptert fra den angitte IP-adressen (i dette tilfellet - henholdsvis 5.5.5.5 eller 127.0.0.1), for alle andre IP-adresser denne havnen(i dette tilfellet - henholdsvis 5000 eller 9000) vil bli stengt. Dette øker den generelle systemsikkerheten, så når det er mulig, bruk alltid dette tilkoblingsstrengformatet for TCP-sockets - hva om systemadministratoren "bare glemmer" å konfigurere brannmuren? Vær oppmerksom på det andre eksemplet - adressen til den samme maskinen (localhost) er angitt der. Dette lar deg lage en TCP-socket på samme maskin hvis du av en eller annen grunn ikke kan bruke Unix-domene-sockets (for eksempel fordi webserverens chroot og FastCGI-programmet chroot er i forskjellige mapper og ikke har felles filstier ). Dessverre kan du ikke spesifisere to eller flere forskjellige IP-adresser, så hvis du virkelig trenger å godta forespørsler fra flere webservere på forskjellige datamaskiner, må du enten åpne porten helt (se forrige metode) og stole på innstillingene til brannmuren din, eller bruk flere sockets på forskjellige porter. Dessuten støtter ikke libfcgi-biblioteket IPv6-adresser - tilbake i 1996 ble denne standarden nettopp født, så du må begrense appetitten til vanlige IPv4-adresser. Riktignok, hvis du virkelig trenger IPv6-støtte, er det relativt enkelt å legge det til ved å lappe FCGX_OpenSocket-funksjonen – biblioteklisensen tillater dette.
Merk følgende!Å bruke funksjonen til å spesifisere en IP-adresse når du oppretter en socket er ikke tilstrekkelig beskyttelse - IP-spoofing-angrep (erstatter IP-adressen til pakkesenderen) er mulig, så det er fortsatt nødvendig å sette opp en brannmur. Vanligvis, som et forsvar mot IP-spoofing, kontrollerer brannmuren samsvaret mellom pakkens IP-adresse og MAC-adressen nettverkskort for alle våre verter lokalt nettverk(mer presist, for kringkastingsdomenet med verten vår), og forkaster alle pakker som kommer fra Internett, returadresse som er plassert i sonen med private IP-adresser eller lokal vert (masker 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7, 127.0.0.0/8 og::1/128 ). Det er imidlertid fortsatt bedre å bruke denne bibliotekfunksjonen - i tilfelle en feilkonfigurert brannmur er det mye vanskeligere å sende en "dødspakke" fra en forfalsket IP-adresse enn fra noen annen, siden TCP-protokollen har innebygd beskyttelse mot IP-spoofing.
Den siste typen tilkoblingsstreng er å bruke vertens domenenavn:
"example.com:5000" "localhost:9000"
I dette tilfellet vil IP-adressen hentes automatisk basert på domenenavnet til verten du spesifiserte. Restriksjonene er fortsatt de samme - verten må ha én IPv4-adresse, ellers vil det oppstå en feil. Men gitt at kontakten opprettes en gang helt i begynnelsen av arbeidet med FastCGI, er det lite sannsynlig at denne metoden vil være veldig nyttig - dynamisk endring av IP-adressen vil fortsatt ikke fungere (mer presist, etter hver endring av IP-adressen du vil ha for å starte FastCGI-programmet på nytt). På den annen side vil kanskje dette være nyttig for relativt stort nettverk- Å huske et domenenavn er fortsatt enklere enn å huske en IP-adresse.
Den andre parameteren i backlog-funksjonen spesifiserer lengden på socket-forespørselskøen. Spesiell betydning 0 (null) indikerer standard kølengde for dette operativsystemet.
Hver gang en forespørsel kommer fra webserveren, blir en ny tilkobling plassert i denne køen som venter på å bli behandlet av vårt FastCGI-program. Hvis køen er helt full, vil alle påfølgende tilkoblingsforespørsler mislykkes - webserveren vil motta et svar som er nektet tilkobling. I prinsippet er det ikke noe galt med dette - Nginx webserver det er en kø med forespørsler, og hvis det ikke er ledige ressurser, vil nye forespørsler vente på sin tur for behandling allerede i nettserverkøen (i hvert fall til ventetiden går ut). I tillegg, hvis du har flere servere som kjører FastCGI, kan Nginx sende en slik forespørsel til en mindre belastet server.
Så la oss prøve å finne ut hva den optimale kølengden vil være. Generelt er det bedre å konfigurere denne parameteren individuelt basert på dataene belastningstesting, men vi vil prøve å anslå det mest passende området for denne verdien. Det første du trenger å vite er at den maksimale kølengden er begrenset (bestemt av operativsystemets kjerneinnstillinger, vanligvis ikke mer enn 1024 tilkoblinger). For det andre bruker køen ressurser, billige, men fortsatt ressurser, så du bør ikke gjøre den urimelig lang. Deretter, la oss si at FastCGI-programmet vårt har 8 arbeidertråder (ganske realistisk for moderne 4-8 kjerneprosessorer), og hver tråd trenger egen forbindelse- oppgaver behandles parallelt. Dette betyr ideelt sett at vi allerede har 8 forespørsler fra webserveren for å umiddelbart, uten unødvendige forsinkelser, kunne gi arbeid til alle tråder. Med andre ord, minimum forespørselskøstørrelse er antall FastCGI-programarbeidertråder. Du kan prøve å øke denne verdien med 50%-100% for å gi litt takhøyde for lasten, siden tiden for dataoverføring over nettverket er begrenset.
La oss nå bestemme den øvre grensen for denne verdien. Her må vi vite hvor mange forespørsler vi faktisk kan behandle og begrense forespørselskøen til denne verdien. Tenk deg at du har gjort denne køen for stor – så mye at kundene dine rett og slett blir lei av å vente på tur og de forlater nettstedet ditt uten å vente på svar. Det er åpenbart ikke noe godt med dette - webserveren måtte sende en forespørsel om å åpne en tilkobling, noe som i seg selv er dyrt, og deretter lukke denne tilkoblingen bare fordi FastCGI-programmet ikke hadde nok tid til å behandle denne forespørselen. Kort sagt, vi kaster bare bort prosessortid, men vi har bare ikke nok av det! Men dette er ikke det verste - det er verre når klienten nekter å motta informasjon fra nettstedet ditt allerede etter at forespørselen har begynt å bli behandlet. Det viser seg at vi må behandle en i det vesentlige ubrukelig forespørsel fullstendig, som du skjønner bare vil forverre situasjonen. Teoretisk sett kan det oppstå en situasjon der de fleste klientene ikke vil vente på svar når prosessoren din er 100 % lastet. Ikke bra.
Så la oss si at vi kan behandle én forespørsel på 300 millisekunder (det vil si 0,3 sekunder). Deretter vet vi at i gjennomsnitt forlater 50 % av besøkende en ressurs hvis en nettside tar mer enn 30 sekunder å laste. Det er klart at 50 % av misfornøyde er for mange, så vi vil begrense maksimal tid siden lastes inn på 5 sekunder. Dette betyr at en helt ferdig nettside - etter bruk av cascading style sheets og utføring av JavaScript - kan dette stadiet på et gjennomsnittlig nettsted ta 70 % av den totale innlastingstiden til en nettside. Så det er ikke mer enn 5 minutter igjen for å laste data over nettverket *0,3 = 1,5 sekunder Deretter bør du huske at html-koden, stilark, skript og grafikk overføres i forskjellige filer, og først html-koden, og deretter alt annet, men etter å ha mottatt html-koden nettleseren begynner å be om de gjenværende ressursene parallelt, slik at vi kan estimere lastetiden til html-koden som 50 % av den totale tiden for å motta data. Så vi har ikke mer enn 1,5 * 0,5 = 0,75 sekunder igjen til å behandle en forespørsel. Hvis i gjennomsnitt én tråd behandler en forespørsel på 0,3 sekunder, bør det være 0,75/0,3 = 2,5 forespørsler per tråd Siden vi har 8 arbeidertråder, bør den resulterende køstørrelsen være 2,5. Jeg vil merke meg at beregningene ovenfor er betingede - hvis du har et spesifikt nettsted, kan verdiene som brukes i beregningen bestemmes mye mer nøyaktig, men det gir fortsatt et utgangspunkt for mer optimal ytelsesjustering.
Så vi har mottatt en socket-deskriptor, hvoretter vi må tildele minne for forespørselsstrukturen. Beskrivelsen av denne strukturen er som følger:
typedef struct FCGX_Request ( int requestId; int role; FCGX_Stream *in; FCGX_Stream *out; FCGX_Stream *err; char **envp; struct Params *paramsPtr; int ipcFd; int isBeginProcessed; int keep flagConnection; int appStatusrit; int listen_sock; int detached ) FCGX_Request;
Merk følgende! Etter å ha mottatt en ny forespørsel, vil alle tidligere data gå tapt, så om nødvendig langtidslagring data, bruk dyp kopiering (kopier selve dataene, ikke pekere til dataene).
Du bør vite følgende om denne strukturen:
- variablene inn, ut og feil spiller rollen som henholdsvis input-, output- og feilstrømmer. Inndatastrømmen inneholder POST-forespørselsdataene, svaret til FastCGI-programmet (for eksempel http-hoder og html-kode for nettsiden) må sendes til utdatastrømmen, og feilstrømmen vil ganske enkelt legge til en oppføring til webserveren feil logg. I dette tilfellet trenger du ikke bruke feilstrømmen i det hele tatt - hvis du virkelig trenger å logge feil, så er det kanskje bedre å bruke en egen fil for dette - overføring av data over nettverket og den påfølgende behandlingen av webserveren bruker ekstra ressurser.
- envp-variabelen inneholder verdiene til miljøvariabler satt av webserveren og http-hoder, for eksempel: SERVER_PROTOCOL, REQUEST_METHOD, REQUEST_URI, QUERY_STRING, CONTENT_LENGTH, HTTP_USER_AGENT, HTTP_COOKIE, HTTP_REFERER og så videre. Disse overskriftene er definert av henholdsvis CGI- og HTTP-protokollstandardene. Eksempler på deres bruk kan finnes i et hvilket som helst CGI-program. Selve dataene er lagret i en rekke strenger, med det siste elementet i matrisen som inneholder en null-peker (NULL) for å indikere slutten av matrisen. Hver linje (hvert element i strengmatrisen) inneholder én variabelverdi i formatet VARIABLE_NAME=VALUE, for eksempel: CONTENT_LENGTH=0 (i dette tilfellet betyr det at denne forespørselen ikke har POST-data, siden lengden er null). Hvis envp-strengmatrisen ikke inneholder overskriften du trenger, betyr det at den ikke ble overført. Hvis du ønsker å få alle variabelverdiene sendt til FastCGI-programmet, les ganske enkelt alle linjene i envp-matrisen i en løkke til du møter en peker til NULL.
Faktisk er vi ferdige med beskrivelsen av denne strukturen - du trenger ikke alle de andre variablene.
Minnet er tildelt, nå må du initialisere forespørselsstrukturen:
int FCGX_InitRequest(FCGX_Request *forespørsel, int sokk, int flagg);
Funksjonsparametrene er som følger:
request - peker til datastrukturen som skal initialiseres
sock er socket-beskrivelsen som vi mottok etter å ha kalt FCGX_OpenSocket-funksjonen. Jeg vil merke at i stedet for en ferdig deskriptor, kan du passere 0 (null) og motta en sokkel med standardinnstillinger, men for oss er denne metoden ikke i det hele tatt interessant - kontakten vil bli åpnet på en tilfeldig ledig port , som betyr at vi ikke vil kunne konfigurere weben vår riktig -server - vi vet ikke på forhånd hvor nøyaktig dataene skal sendes.
flagg - flagg. Faktisk kan bare ett flagg sendes til denne funksjonen - FCGI_FAIL_ACCEPT_ON_INTR - ikke kall FCGX_Accept_r når du bryter.
Etter dette må du få ny forespørsel:
int FCGX_Accept_r(FCGX_Request *request);
Du må legge inn forespørselsstrukturen som allerede er initialisert på forrige trinn. Merk følgende! I et flertråds program må du bruke synkronisering når du kaller denne funksjonen.
Faktisk gjør denne funksjonen alt arbeidet med sockets: først sender den et svar til webserveren på forrige forespørsel (hvis det var en), lukker den forrige datakanalen og frigir alle ressurser knyttet til den (inkludert forespørselsstrukturvariabler) , så mottar den en ny forespørsel, åpner en ny datakanal og klargjør nye data i forespørselsstrukturen for påfølgende behandling. Hvis det er en feil ved mottak av en ny forespørsel, returnerer funksjonen en feilkode som er mindre enn null.
Neste må du sannsynligvis få Miljøvariabler, for dette kan du enten behandle request->envp-arrayet selv, eller bruke funksjonen
char *FCGX_GetParam(const char *navn, FCGX_ParamArray envp);
der navn er en streng som inneholder navnet på miljøvariabelen eller http-overskriften hvis verdi du ønsker å få,
envp - en rekke miljøvariabler som er inneholdt i request->envp-variabelen
Funksjonen returnerer verdien til miljøvariabelen vi trenger som en streng. La den oppmerksomme leseren ikke bli skremt av typen misforhold mellom char ** og FCGX_ParamArray - disse typene er erklært synonymer (typedef char **FCGX_ParamArray).
I tillegg må du sannsynligvis sende et svar til webserveren. For å gjøre dette, må du bruke utgangsstrømmen for forespørsel->ut og funksjonen
int FCGX_PutStr(const char *str, int n, FCGX_Stream *stream);
der str er en buffer som inneholder dataene som skal sendes ut, uten den avsluttende nullverdien (det vil si at bufferen kan inneholde binære data),
n - bufferlengde i byte,
stream - strømmen som vi ønsker å sende ut data til (request->out eller request->err).
Hvis du bruker standard nullterminerte C-strenger, vil det være mer praktisk å bruke funksjonen
int FCGX_PutS(const char *str, FCGX_Stream *stream);
som ganske enkelt vil bestemme lengden på strengen ved å bruke strlen(str) og call forrige funksjon. Derfor, hvis du vet lengden på strengen på forhånd (du bruker for eksempel C++ std::strings), er det bedre å bruke den forrige funksjonen av effektivitetshensyn.
Jeg vil merke meg at disse funksjonene fungerer perfekt med UTF-8-strenger, så det burde ikke være noen problemer med flerspråklige nettapplikasjoner.
Du kan også ringe disse funksjonene flere ganger mens du behandler samme forespørsel, i noen tilfeller kan dette forbedre ytelsen. Du må for eksempel sende noen stor fil. I stedet for å laste ned hele denne filen fra harddisken og deretter sende den i ett stykke, kan du begynne å sende data med en gang. Som et resultat, klienten i stedet hvit skjerm Nettleseren vil begynne å motta dataene den er interessert i, noe som rent psykologisk vil tvinge den til å vente litt lenger. Med andre ord, du får litt tid før siden lastes. Jeg vil også merke meg at de fleste ressurser (cascading style sheets, JavaScript, etc.) er angitt i begynnelsen av nettsiden, det vil si at nettleseren vil kunne analysere deler av html-koden og begynne å laste disse ressursene tidligere - enda en grunn til å vise data i deler.
Det neste du må gjøre er å behandle POST-forespørselen. For å få verdien, må du lese data fra forespørselen->in stream ved å bruke funksjonen
int FCGX_GetStr(char * str, int n, FCGX_Stream *stream);
der str er en peker til bufferen,
n - bufferstørrelse i byte,
stream - strømmen som vi leser data fra.
Størrelsen på de overførte dataene i en POST-forespørsel (i byte) kan bestemmes ved hjelp av miljøvariabelen CONTENT_LENGTH, hvis verdi, som vi husker, kan oppnås ved å bruke FCGX_GetParam-funksjonen. Merk følgende!Å lage en str-buffer basert på verdien av variabelen CONTENT_LENGTH uten noen restriksjoner er en veldig dårlig idé: enhver angriper kan sende en hvilken som helst POST-forespørsel, uansett hvor stor, og serveren din kan ganske enkelt gå tom for ledig plass RAM(det vil vise seg å være et DoS-angrep, hvis du vil). I stedet er det bedre å begrense bufferstørrelsen til en rimelig verdi (fra noen få kilobyte til flere megabyte) og kalle opp FCGX_GetStr-funksjonen flere ganger.
Den siste viktige funksjonen blinker utdata- og feilstrømmene (sender til klienten de fortsatt usendte dataene som vi klarte å plassere i utdata- og feilstrømmene) og lukker forbindelsen:
void FCGX_Finish_r(FCGX_Request *request);
Jeg vil spesielt merke meg at denne funksjonen er valgfri: FCGX_Accept_r-funksjonen sender også data til klienten og lukker gjeldende tilkobling før den mottar en ny forespørsel. Spørsmålet oppstår: hvorfor er det nødvendig? Tenk deg at du allerede har sendt klienten alle nødvendige data, og nå må du utføre noen siste operasjoner: skrive statistikk til databasen, feil i loggfilen, etc. Det er klart at tilkoblingen til klienten ikke lenger er nødvendig, men klienten (som betyr nettleseren) venter fortsatt på informasjon fra oss: hva om vi sender noe annet? Det er åpenbart at vi ikke kan ringe FCGX_Accept_r på forhånd - etter dette må vi begynne å behandle neste forespørsel. Det er i dette tilfellet at du vil trenge FCGX_Finish_r-funksjonen - den vil tillate deg å lukke gjeldende tilkobling før du mottar en ny forespørsel. Ja, vi vil kunne behandle samme antall forespørsler per tidsenhet som uten å bruke denne funksjonen, men klienten vil få svar tidligere - han trenger ikke lenger å vente på slutten av vår endelige operasjon, og det er nettopp på grunn av den høyere forespørselsbehandlingshastigheten som vi bruker FastCGI.
Dette avslutter faktisk beskrivelsen av bibliotekets funksjoner og begynner behandlingen av de mottatte dataene.
Et enkelt eksempel på et flertråds FastCGI-program
Jeg tror alt vil være klart i eksemplet. Det eneste er at utskrift av feilsøkingsmeldinger og "sove" i arbeidertråden gjøres utelukkende for demonstrasjonsformål. Når du kompilerer programmet, ikke glem å inkludere libfcgi- og libpthread-bibliotekene (gcc-kompilatoralternativer: -lfcgi og -lpthread).#inkludere Forespørsel akseptert fra verten ", request.out); FCGX_PutS(servernavn ? servernavn: "?", request.out); FCGX_PutS("FastCGI Hei! (flertrådig C, fcgiapp-bibliotek)
\r\n", request.out); FCGX_PutS("
\r\n", request.out); FCGX_PutS("\r\n", request.out); //"sovner" - imitasjon av et flertrådsmiljø sleep(2); //lukk gjeldende tilkobling FCGX_Finish_r(&request); //endelige handlinger - registrering av statistikk, logging feil, etc. ) return NULL; ) int main(void) ( int i; pthread_t id; //initialiser biblioteket FCGX_Init(); printf("Lib er startet\n"); //åpne en ny socket socketId = FCGX_OpenSocket (SOCKET_PATH, 20); if(socketId< 0)
{
//ошибка при открытии сокета
return 1;
}
printf("Socket is opened\n");
//создаём рабочие потоки
for(i = 0; i < THREAD_COUNT; i++)
{
pthread_create(&id[i], NULL, doit, NULL);
}
//ждем завершения рабочих потоков
for(i = 0; i < THREAD_COUNT; i++)
{
pthread_join(id[i], NULL);
}
return 0;
}
Enkelt Nginx-konfigurasjonseksempel
Faktisk ser det enkleste eksemplet på en konfigurasjon slik ut:Server ( server_name localhost; plassering / ( fastcgi_pass 127.0.0.1:9000; #fastcgi_pass unix:/tmp/fastcgi/mysocket; #fastcgi_pass localhost:9000; include fastcgi_params; ) )
I dette tilfellet er denne konfigurasjonen nok for riktig drift vårt FastCGI-program. De kommenterte linjene er et eksempel på arbeid med Unix-domene-sockets og spesifisering av et domenevertsnavn i stedet for en IP-adresse.
Etter å ha kompilert og kjørt programmet, og Nginx-innstillinger Jeg har en stolt inskripsjon på min lokale vertsadresse:
FastCGI Hei! (flertrådig C, fcgiapp-bibliotek)
Takk til alle som leste til slutt.
Kapittel #9.
Programmering med CGI
Å inkludere en del om CGI i en bok om databaser kan virke like rart som å inkludere et kapittel om bilreparasjon i en kokebok. For å gå til matbutikken trenger du selvfølgelig en fungerende bil, men er det hensiktsmessig å snakke om dette? En fullstendig diskusjon av CGI og webprogrammering generelt ligger utenfor denne bokens omfang, men kort introduksjon Disse emnene er nok til å utvide mulighetene til MySQL og mSQL for å presentere data på nettet.
Dette kapittelet er først og fremst ment for de som studerer databaser, men som ønsker å få litt kunnskap om webprogrammering. Hvis etternavnet ditt er Berners-Lee eller Andreessen, vil du neppe finne noe her som du ikke allerede vet. Men selv om du ikke er ny til CGI, kan det være ganske nyttig å ha en rask referanse mens du dykker ned i mysteriene til MySQL og mSQL.
Hva er CGI?
Som de fleste akronymer sier ikke Common Gateway Interface (CGI) så mye. Grensesnitt med hva? Hvor er denne porten? Hva slags fellesskap snakker vi om? For å svare på disse spørsmålene, la oss gå litt tilbake og ta en titt på WWW som helhet.
Tim Berners-Lee, en fysiker som jobbet ved CERN, kom opp med ideen om nettet i 1990, selv om planen dateres tilbake til 1988. Ideen var å gjøre det mulig for partikkelfysikkforskere å enkelt og raskt dele multimediedata - tekst , bilder og lyd - gjennom Internett. WWW besto av tre hoveddeler: HTML, URL og HTTP. HTML - Et formateringsspråk som brukes til å presentere innhold på nettet. URL - dette er adressen som brukes til å hente HTML (eller annet) innhold fra webserveren. Og endelig HTTP - det er et språk som forstås av webserveren og lar klienter be om dokumenter fra serveren.
Muligheten til å sende informasjon av alle typer over Internett var revolusjonerende, men en annen mulighet ble snart oppdaget. Hvis du kan sende hvilken som helst tekst via nettet, hvorfor kan du da ikke sende tekst opprettet av et program, og ikke hentet fra ferdig fil? Dette åpner et hav av muligheter. Et enkelt eksempel: du kan bruke et program som skriver ut nåværende tid, slik at leseren ser riktig tidspunkt hver gang de ser på siden. Flere smarte hoder ved National Center for Supercomputing Applications ( Nasjonalt senter Supercomputer Application Development - NCSA), som laget en webserver, så denne muligheten, og CGI dukket snart opp.
CGI er et sett med regler som lar programmer på en server sende data til klienter gjennom en webserver. CGI-spesifikasjonen ble ledsaget av endringer i HTML og HTTP som introduserte en ny funksjon kjent som skjemaer.
Hvis CGI tillater programmer å sende data til en klient, utvider skjemaer denne muligheten ved å la klienten sende data til det CGI-programmet. Nå kan brukeren ikke bare se gjeldende tid, men også stille klokken! CGI-former har åpnet døren til ekte interaktivitet i nettverdenen. Vanlige CGI-applikasjoner inkluderer:
- Dynamisk HTML. Hele nettsteder kan genereres av ett CGI-program.
- Søkemotorer som finner dokumenter som inneholder brukerspesifiserte ord.
- Gjestebøker og oppslagstavler der brukere kan legge til meldingene sine.
- Bestillingsskjemaer.
- Spørreskjemaer.
- Henter informasjon fra en database som ligger på serveren.
I påfølgende kapitler vil vi diskutere alle disse CGI-applikasjonene, så vel som noen andre. De gir alle en flott måte å koble CGI til en database, som er det vi er interessert i i denne delen.
HTML-skjemaer
Før du utforsker detaljene til CGI, er det nyttig å se på den vanligste måten sluttbrukere gir et grensesnitt til CGI-programmer på: HTML-skjemaer. Skjemaer er en del HTML-språk, gir slutt bruker Enger forskjellige typer. Data som legges inn i felt kan sendes til webserveren. Felter kan brukes til å skrive inn tekst eller være knapper som brukeren kan klikke eller sjekke. Her er et eksempel på en HTML-side som inneholder et skjema:
<НТМL><НЕАD><ТITLЕ>Min skjemaside
<р>Dette er en side med et skjema.
Dette skjemaet lager en streng på 40 tegn der brukeren kan skrive inn navnet sitt. Under inngangslinjen er det en knapp, når den klikkes, overføres skjemadataene til serveren. Nedenfor er skjemarelaterte koder som støttes av HTML 3.2, den mest brukte standarden i dag. Tag- og attributtnavn kan angis uansett, men vi følger den valgfrie konvensjonen om at startkoder skrives med store bokstaver og avsluttende tagger skrives med små bokstaver.
Den eneste inndatatypen vi ikke har brukt her er IMAGE-typen for taggen . Det kan brukes som en alternativ metode for innsending av skjema. Imidlertid er IMAGE-typen sjelden kompatibel med tekstbaserte og mindre responsive nettlesere, så det er lurt å unngå det med mindre nettstedet ditt har en grafisk-tung stil.
Når du har lært det grunnleggende om HTML-skjemaer, kan du begynne å lære om selve CGI.
CGI-spesifikasjon
Så hva er egentlig "reglene" som lar et CGI-program i for eksempel Batavia, Illinois, kommunisere med en nettleser i Ytre Mongolia? Den offisielle CGI-spesifikasjonen, sammen med et vell av annen informasjon om CGI, kan finnes på NCSA-serveren på http://hoohoo . ncsa.uluc.edu/cgi/. Dette kapittelet eksisterer imidlertid av denne grunn, slik at du ikke trenger å reise lenge og lete etter det selv.
Det er fire måter som CGI sender data mellom CGI-npor-rammen og webserveren, og dermed webklienten:
- Miljøvariabler.
- Kommandolinje.
- Standard inndataenhet.
- Standard utgangsenhet.
Med disse fire metodene videresender serveren alle dataene som sendes av klienten til CGI-programmet. CGI-programmet gjør deretter sin magi og sender utdataene tilbake til serveren, som videresender det til klienten.
Disse dataene er basert på Apache HTTP-serveren. Apache er den vanligste webserveren som kjører på nesten alle plattformer, inkludert Windows 9x og Windows NT. De kan imidlertid gjelde for alle HTTP-servere som støtter CGI. Noen proprietære servere, for eksempel de fra Microsoft og Netscape, kan ha tilleggsfunksjoner eller fungere litt annerledes. Siden ansiktet til nettet fortsetter å endre seg i en utrolig hastighet, er standarder fortsatt i utvikling, og det vil utvilsomt være endringer i fremtiden. Når det gjelder CGI, ser det imidlertid ut til at teknologien er etablert – på bekostning av å bli erstattet av andre teknologier, for eksempel appleter. Eventuelle CGI-programmer du skriver ved hjelp av denne informasjonen vil nesten helt sikkert kunne kjøre i mange år på de fleste webservere.
Når et CGI-program kalles opp gjennom et skjema, det vanligste grensesnittet, sender nettleseren serveren en lang streng som begynner med banen til CGI-programmet og navnet. Dette etterfølges av diverse andre data kalt baneinformasjon, som sendes til CGI-programmet gjennom miljøvariabelen PATH_INFO (Figur 9-1). Baneinformasjonen etterfølges av et "?"-tegn, etterfulgt av skjemadataene, som sendes til serveren ved hjelp av HTTP GET-metoden. Disse dataene gjøres tilgjengelig for CGI-programmet gjennom miljøvariabelen QUERY_STRING. Eventuelle data som siden sender ved hjelp av HTTP POST-metoden, som oftest brukes, vil bli sendt til CGI-programmet via standard enhet input. En typisk streng som en server kan motta fra en nettleser er vist i fig. 9-1. Program navngitt formlest i katalogen cgi-bin kalt opp av serveren med tilleggsbaneinformasjon ekstra informasjon og choice=help request-data - antagelig som en del av den opprinnelige URL-en. Til slutt sendes selve skjemadataene (teksten «CGI-programmering» i «søkeord»-feltet) via HTTP POST-metoden.
Miljøvariabler
Når serveren kjører et CGI-program, sender den først noen data som skal kjøres i form av miljøvariabler. Spesifikasjonen definerer offisielt sytten variabler, men mange flere brukes uformelt gjennom mekanismen beskrevet nedenfor, kalt HTTP_/nec/zams/n. CGI-program
har tilgang til disse variablene på samme måte som alle shell-miljøvariabler når de kjøres fra kommandolinjen. I et shell-skript, for eksempel, kan miljøvariabelen F00 nås som $F00; i Perl ser dette kallet ut som $ENV("F00"); i C - getenv("F00"); osv. Tabell 9-1 viser variablene som alltid settes av serveren - selv om de er null. I tillegg til disse variablene, blir dataene som returneres av klienten i forespørselshodet tilordnet variabler av formen HTTP_F00, der F00 er navnet på overskriften. For eksempel inkluderer de fleste nettlesere versjonsinformasjon i en overskrift kalt USEfl_AGENT. Din CGI-npor-ramma kan hente disse dataene fra HTTP_USER_AGENT-variabelen.
Tabell 9-1.CGI-miljøvariabler
Miljøvariabel |
Beskrivelse |
||
CONTENT_LENGTH |
Lengde på data overført ved bruk av POST- eller PUT-metoder, i byte. |
||
INNHOLDSTYPE |
MIME-typen til dataene vedlagt ved bruk av POST- eller PUT-metodene. |
||
GATEWAY_INTERFACE |
Versjonsnummeret til CGI-spesifikasjonen som støttes av serveren. |
||
PATH_INFO |
Ytterligere baneinformasjon sendt av klienten. For eksempel for forespørselen http://www.myserver.eom/test.cgi/this/is/a/ sti?felt=grønn verdien av variabelen PATH_ INFO vil være /dette/er/en/bane. |
||
PATH_TRANSLATED |
Samme som PATH_INFO, men serveren produserer alle |
||
|
Mulig oversettelse, for eksempel navneutvidelser som "-konto". » |
||
QUERY_STRING |
Alle data etter "?" i URL. Dette er også dataene som sendes når skjemaets REQ-UEST_METHOD er GET. |
||
REMOTE_ADDR |
IP-adressen til klienten som sender forespørselen. |
||
REMOTE_HOST |
Vertsnavnet til klientmaskinen, hvis tilgjengelig. |
||
REMOTE_IDENT |
Hvis webserveren og klienten støtter typeidentifikasjon identd så er dette brukernavnet til kontoen som sender forespørselen. |
||
REQUEST_METHOD |
Metoden klienten bruker for å gjøre forespørselen. For CGI-programmene vi skal lage vil dette vanligvis være POST eller GET. |
||
SERVER NAVN | Vertsnavnet – eller IP-adressen hvis ikke noe navn er tilgjengelig – til maskinen som kjører webserveren. | ||
SERVER_PORT | Portnummeret som brukes av webserveren. | ||
SERVER_PROTOCOL |
Protokollen som brukes av klienten for å kommunisere med serveren. I vårt tilfelle er denne protokollen nesten alltid HTTP. | ||
SERVER_SOFTWARE | Informasjon om versjonen av webserveren som kjører CGI-programmet. | ||
SCRIPT_NAME |
Banen til skriptet som skal kjøres, som spesifisert av klienten. Kan brukes når en URL refererer til seg selv, og slik at skript referert på forskjellige steder kan utføres forskjellig avhengig av plasseringen. |
||
Her er et eksempel på et CGI Perl-skript som skriver ut alle miljøvariablene satt av serveren, samt eventuelle arvede variabler, for eksempel PATH, satt av skallet som startet serveren.
#!/usr/bin/perl -w
skrive ut<< HTML;
Innholdstype: tekst/html\n\n
HTML
foreach (nøkler %ENV) ( skriv ut "$_: $ENV($_)
\n"; )
skrive ut<
HTML
Alle disse variablene kan brukes og til og med modifiseres av CGI-programmet ditt. Disse endringene påvirker imidlertid ikke webserveren som kjører programmet.
Kommandolinje
CGI lar argumenter sendes til CGI-programmet som kommandolinjeparametere, som sjelden brukes. Den brukes sjelden fordi dens praktiske anvendelser er få, og vi vil ikke dvele ved den i detalj. Poenget er at hvis miljøvariabelen QUERY_STRING ikke inneholder tegnet "=", vil CGI-programmet bli utført med kommandolinjeparameterne hentet fra QUERY_STRING. For eksempel, http://www.myserver.com/cgi- bin/finger?root vil kjøre fingerrot på www.minserver.com.
Det er to hovedbiblioteker som gir et CGI-grensesnitt til Perl. Den første er cgi-lib.pl Nytte cgi-lib.pl veldig vanlig fordi det lenge var det eneste store biblioteket som var tilgjengelig. Den er designet for å fungere i Perl 4, men fungerer med Perl 5. Det andre biblioteket, CGI.pm, nyere og på mange måter overlegen cgi-lib.pl. CGI.pm skrevet for Perl 5 og bruker en fullstendig objektorientert design for arbeid med CGI-data. Modul CGI.pm analyserer standardinndataenheten og QUERY_STRING-variabelen og lagrer dataene i et CGI-objekt. Programmet ditt trenger bare å lage et nytt CGI-objekt og bruke enkle metoder som paramQ for å hente dataene du trenger. Eksempel 9-2 fungerer som en kort demonstrasjon av hvordan CGI.pm tolker dataene. Alle Perl-eksempler i dette kapittelet vil bruke CGI.pm.
Eksempel 9-2.
Parsing CGI-data i Perl
#!/usr/bin/perl -w
bruk CGI qw(:standard);
# CGI.pm-modulen brukes. qw(:standard) importerer
# navneområde for standard CGI-funksjoner å få
# klarere kode. Dette kan gjøres hvis i skriptet
# bare ett CGI-objekt brukes.
$mycgi = ny CGI; #Opprett et CGI-objekt som skal være inngangsporten til skjemadataene
@fields = $mycgi->param; # Hent navnene på alle utfylte skjemafelt
print header, start_html("CGI.pm test"); ft Metoder "header" og "start_html",
# sørget for
# CGI.pm, gjør det enklere å få HTML.
# "header" gir ut den nødvendige HTTP-headeren, en
#"start_html" gir ut en HTML-header med det gitte navnet,
#a er også en tag
.skrive ut "<р>Skjemadata:
";
foreach (@fields) ( skriv ut $_, ":",- $mycgi->param($_), "
"; }
# For hvert felt, skriv ut navnet og verdien oppnådd med
#
$mycgi->param("feltnavn").
print end_html; # Stenografi for visning av sluttkoder "
".
Behandler inndata i C
Siden kjerne-API-ene for MySQL og mSQL er skrevet i C, vil vi ikke helt forlate C til fordel for Perl, men vi vil gi noen C-eksempler der det er aktuelt. Det er tre mye brukte C-biblioteker for CGI-programmering: cgic Tom Boutell*; cgihtml Eugene Kim og libcgi fra EIT*. Vi tror at cgic er den mest komplette og enkle å bruke. Det den derimot mangler er muligheten til å liste opp alle formvariablene når du ikke kjenner dem på forhånd. Faktisk kan det legges til med en enkel oppdatering, men det er utenfor rammen av dette kapittelet. Derfor bruker vi i eksempel 9-3 biblioteket cgihtml, for å gjenta Perl-skriptet ovenfor i C.
Eksempel 9-3.Parsing av CGI-data i C
/*
cgihtmltest.c - Typisk CGI-program for å vise nøkler og deres verdier
fra data mottatt fra skjemaet */
#inkludere
#include "cgi-lib.h" /* Dette inneholder alle CGI-funksjonsdefinisjoner */
#include "html-lib.h" /* Dette inneholder "alle HTML-hjelpefunksjonsdefinisjoner */
void print_all(lliste 1)
/* Disse funksjonene sender ut dataene som sendes inn av skjemaet i samme format som Perl-skriptet ovenfor. Cgihtml har også en innebygd funksjon
Print_entries(), som gjør det samme ved å bruke HTML-listeformat. */ (
node*vindu;
/* "Node"-typen er definert i cgihtml-biblioteket og refererer til en koblet liste som lagrer alle skjemadataene. */
vindu = I.hode; /* Setter en peker til begynnelsen av skjemadataene */
while (vindu != NULL) ( /* Gå gjennom den koblede listen til det siste (første tomme) elementet */
printf(" %s:%s
\n",vindu->oppføring.navn,erstatt_ltgt(vindu->oppføring.verdi));
/* Skriv ut data. Replace__ltgt() er en funksjon som forstår HTML-kodingen av tekst og sørger for at den sendes ut riktig til klientnettleseren. */
vindu = vindu->neste; /* Flytt til neste listeelement. */
} }
int main() (
lister oppføringer; /* Peker til analyserte data*/
int status; /* Heltall som representerer status */
Html__header(); /* Hjelpe HTML-funksjon, sender ut HTML-overskriften*/
Html_begin("cgihtml test");
/* En HTML-hjelpefunksjon som skriver ut begynnelsen av en HTML-side med den angitte tittelen. */
status = read_cgi_input(&oppføringer); /* Skriver inn og analyserer skjemadata*/
Printf("<р>Skjemadata:
");
Print_all(oppføringer); /* Kaller print_all() funksjonen definert ovenfor. */
html_end(); /* HTML-hjelpefunksjon som skriver ut slutten av HTML-siden. */
Liste_clear(&oppføringer); /* Frigjør minne okkupert av skjemadata. */
returner 0; )
Standard utgangsenhet
Dataene som sendes av CGI-programmet til standardutgangsenheten leses av webserveren og sendes til klienten. Hvis skriptnavnet begynner med nph-, da sendes dataene direkte til klienten uten innblanding fra webserveren. I dette tilfellet må CGI-programmet generere riktig HTTP-header som klienten vil forstå. I ellers la webserveren generere HTTP-headeren for deg.
Selv om du ikke bruker nph-scenario, serveren må gis ett direktiv som vil fortelle den informasjon om utdataene dine. Dette er vanligvis Content-Type HTTP-headeren, men kan også være Location-headeren. Overskriften må følges av en tom linje, det vil si en linjemating eller en CR/LF-kombinasjon.
Content-Type-overskriften forteller serveren hvilken type data CGI-programmet ditt produserer. Hvis dette er en HTML-side, bør strengen være Content-Type: tekst/html. Plasseringsoverskriften forteller serveren en annen URL - eller en annen bane på samme server - hvor klienten skal henvises. Overskriften skal se slik ut: Sted: http:// www. min server. no/et annet/sted/.
Etter HTTP-hodene og en tom linje kan du sende de faktiske dataene som er produsert av programmet ditt - en HTML-side, et bilde, tekst eller noe annet. Blant CGI-programmene som følger med Apache server, Det er nph-test-cgi Og test-cgi som på en fin måte viser forskjellen mellom henholdsvis nph- og ikke-nph-stiloverskrifter.
I denne delen vil vi bruke biblioteker CGI.pm Og cgic, som har funksjoner for å sende ut både HTTP- og HTML-hoder. Dette vil tillate deg å fokusere på å sende ut det faktiske innholdet. Disse hjelpefunksjonene er brukt i eksemplene gitt tidligere i dette kapittelet.
Viktige funksjoner i CGI-skript
Du vet allerede i utgangspunktet hvordan CGI fungerer. Klienten sender data, vanligvis ved hjelp av et skjema, til webserveren. Serveren kjører CGI-programmet og sender data til det. CGI-programmet utfører sin prosessering og returnerer utdataene til serveren, som sender den videre til klienten. Nå fra å forstå hvordan CGI npor-rammer fungerer, må vi gå videre til å forstå hvorfor de er så mye brukt.
Selv om du allerede vet nok fra dette kapittelet til å kunne sette sammen et enkelt fungerende CGI-program, er det noen flere ting du trenger å vite. viktige saker før du oppretter faktisk fungerende programmer for MySQL eller mSQL. Først må du lære å jobbe med flere skjemaer. Deretter må du lære noen sikkerhetstiltak som vil forhindre at angripere ulovlig får tilgang til eller ødelegger serverfilene dine.
Lagring av staten
Statlig memorering er et viktig middel for å gi god service brukerne dine, og tjener ikke bare til å bekjempe forherdede kriminelle, slik det kan virke. Problemet er forårsaket av det faktum at HTTP er en såkalt "minneløs" protokoll. Dette betyr at klienten sender data til serveren, serveren returnerer data til klienten, og så går alle sin vei. Serveren lagrer ikke data om klienten som kan være nødvendig i etterfølgende operasjoner. Likeledes er det ingen garanti for at klienten vil beholde noen data om transaksjonen som kan brukes senere. Dette setter en umiddelbar og betydelig begrensning på bruken av World Wide Web.
Å skrive CGI med denne protokollen ligner på å ikke kunne huske en samtale. Når du snakker med noen, uansett hvor ofte du har snakket med dem før, må du presentere deg selv og se etter et felles samtaleemne. Det er ikke nødvendig å forklare at dette ikke bidrar til produktiviteten. Figur 9-2 viser at når en forespørsel når et CGI-program, er den fullstendig nytt eksemplar program som ikke har noen forbindelse med det forrige.
På klientsiden, med bruken av Netscape Navigator, dukket det opp en tilsynelatende raskt laget løsning kalt informasjonskapsler. Den består av å lage en ny HTTP-header som kan sendes frem og tilbake mellom klienten og serveren, på samme måte som Content-Type og Location-hodene. Klientens nettleser, når den mottar informasjonskapselhodet, må lagre den i informasjonskapseldata, samt navnet på domenet der denne informasjonskapselen opererer. Deretter, hver gang en URL innenfor det angitte domenet besøkes, må en cookie-header returneres til serveren for bruk av CGI-programmer på den serveren.
Informasjonskapselmetoden brukes hovedsakelig til å lagre bruker-ID. Informasjon om den besøkende kan lagres i en fil på servermaskinen. Denne brukerens unike ID kan sendes som en informasjonskapsel til brukerens nettleser, og hver gang brukeren besøker siden, sender nettleseren automatisk denne IDen til serveren. Serveren sender IDen til CGI-programmet, som åpner den tilsvarende filen og får tilgang til alle data om brukeren. Alt dette skjer ubemerket av brukeren.
Til tross for nytten av denne metoden, bruker de fleste store nettsteder den ikke som deres eneste måte å huske tilstand på. Det er en rekke årsaker til dette. For det første støtter ikke alle nettlesere informasjonskapsler. Inntil nylig støttet ikke hovednettleseren for personer med begrenset syn (for ikke å nevne personer med utilstrekkelig Internett-tilkoblingshastighet) - Lynx - informasjonskapsler. Den støtter dem fortsatt ikke "offisielt", selv om noen av dens allment tilgjengelige "sidegrener" gjør det. For det andre, og enda viktigere, knytter informasjonskapsler brukeren til en bestemt maskin. En av de store fordelene med nettet er at det er tilgjengelig fra hvor som helst i verden. Uansett hvor nettsiden din ble opprettet eller lagret, kan den vises fra hvilken som helst Internett-tilkoblet maskin. Imidlertid, hvis du prøver å få tilgang til en informasjonskapselaktivert side fra en annens maskin, vil all din personlige informasjon som vedlikeholdes av informasjonskapselen gå tapt.
Mange nettsteder bruker fortsatt informasjonskapsler for å tilpasse brukersider, men de fleste utfyller dem med et tradisjonelt grensesnitt for pålogging/passordstil. Hvis nettstedet åpnes fra en nettleser som ikke støtter informasjonskapsler, inneholder siden et skjema der brukeren skriver inn påloggingsnavnet og passordet som ble tildelt ham da han først besøkte nettstedet. Vanligvis er denne formen liten og upretensiøs, for ikke å skremme av de fleste brukere som ikke er interessert i noen personalisering, men bare ønsker å gå videre. Etter at brukeren har skrevet inn et påloggingsnavn og passord i skjemaet, finner CGI en fil som inneholder data om den brukeren, som om navnet ble sendt med en informasjonskapsel. Ved å bruke denne metoden kan brukeren registrere seg på et personlig tilpasset nettsted fra hvor som helst i verden.
I tillegg til oppgavene med å ta hensyn til brukerpreferanser og langsiktig lagring av informasjon om ham, mer subtilt eksempel huske staten levert av populære søkemotorer. Når du søker ved hjelp av tjenester som AltaVista eller Yahoo, vil du vanligvis få mange flere resultater enn det som kan vises i et lettlest format. Dette problemet løses ved å vise et lite antall resultater - vanligvis 10 eller 20 - og gi noen navigasjonsmuligheter for å se neste gruppe resultater. Selv om denne oppførselen virker normal og forventet for den gjennomsnittlige nettsurferen, er implementeringen av den ikke-triviell og krever statlig lagring.
Når en bruker først gjør et søk til en søkemotor, samler den inn alle resultatene, kanskje begrenset til en forhåndsdefinert grense. Trikset er å produsere disse resultatene i små mengder om gangen, samtidig som du husker hva slags bruker som ba om disse resultatene og hvilken porsjon han forventer neste gang. Ser vi bort fra kompleksiteten til selve søkemotoren, står vi overfor problemet med konsekvent å gi brukeren noe informasjon på én side. Tenk på eksempel 9-4, som viser et CGI-skript som skriver ut ti linjer av en fil og gir den muligheten til å se på neste eller forrige ti linjer.
Eksempel 9-4. Lagrer tilstand i et CGI-skript
#!/usr/bin/perl -w
bruk CGI;
Open(F,"/usr/dict/words") or die("Han kan ikke åpne! $!");
#Dette er filen som skal sendes ut, den kan være hva som helst.
$output = ny CGI;
sub print_range ( # Dette hovedfunksjon programmer, min $start = shift;
# Startlinjen til filen, min $count = 0;
# Peker, min $line = "";
# Gjeldende fillinje, skriv ut $output->header,
$output->start_html("Min ordbok");
#
Produserer HTML med tittelen "Min ordbok", skriv ut "
mens (($count< $start) and ($line =
# Hopp over alle linjene før den første, mens (($count< $start+10) and
($line ?
#
Skriv ut de neste 10 linjene.
min $newnext = $start+10; min $newprev = $start-10;
# Angi innledende strenger for "Neste" og "Forrige" URL-er,
skrive ut "
";
med mindre ($start == 0) ( # Inkluder "Forrige" URL med mindre du
# er ikke lenger i begynnelsen.
print qq%Forrige%; )
med mindre (eof) ( # Inkluder "Neste" URL med mindre du #
ikke på slutten av filen.
print qq%Next%;
}
skriv ut "HTML;
HTML
exit(0); )
# Hvis det ikke er data, start på nytt,
if (ikke $output->param) (
&print_range(0); )
# Ellers, start fra linjen spesifisert i dataene.
&print_range($output->param("start"));
I dette eksemplet lagres tilstanden ved hjelp av den enkleste metoden. Det er ingen problemer med å lagre data, siden vi holder dem i en fil på serveren. Vi trenger bare å vite hvor vi skal starte utdata, så skriptet inkluderer ganske enkelt startpunktet for neste eller forrige gruppe med linjer i URL-en - alt som trengs for å generere neste side.
Men hvis du trenger mer enn bare muligheten til å bla gjennom en fil, kan det være tungvint å stole på en URL. Du kan lindre denne vanskeligheten ved å bruke et HTML-skjema og inkludere tilstandsdata i tagger type SKJULT. Denne metoden har blitt brukt med suksess på mange nettsteder, slik at koblinger kan lages mellom relaterte CGI-programmer eller utvide bruken av et enkelt CGI-program, som i forrige eksempel. I stedet for å lenke til bestemt objekt, for eksempel hjemmesiden, kan URL-dataene peke til en automatisk generert bruker-ID.
Slik fungerer AltaVista og andre søkemotorer. Det første søket genererer en bruker-ID, som er skjult bak kulissene i påfølgende URL-er. Tilknyttet denne IDen er én eller flere filer som inneholder resultatene av spørringen. Nettadressen inneholder ytterligere to verdier: din nåværende posisjon i resultatfilen og retningen du vil navigere videre i den. Disse tre verdiene er alt du trenger for å få det til å fungere kraftige systemer navigering av store søkemotorer.
Det er imidlertid fortsatt noe som mangler. Filen som ble brukt i vårt eksempel /usr/diett/ord veldig stor. Hva om vi gir opp halvveis i lesingen, men ønsker å komme tilbake til det senere? Hvis du ikke husker URL-en til neste side, er det ingen måte å gå tilbake på, ikke engang AltaVista vil tillate det. Hvis du starter datamaskinen på nytt eller bruker en annen datamaskin, vil du ikke kunne gå tilbake til de tidligere søkeresultatene uten å gå inn i søket på nytt. Imidlertid er denne langsiktige tilstandslagringen kjernen i nettsidetilpasningen vi diskuterte ovenfor, og det er verdt å se på hvordan den kan brukes. Eksempel 9-5 er en modifisert versjon av eksempel 9-4.
Eksempel 9-5.
Stabil tilstand memorering
#!/usr/bin/perl -w
bruk CGI;
umask 0;
Open(F,"/usr/dict/words") or die("Han kan ikke åpne! $!");
Chdir("brukere") or die("Jeg kan ikke gå til katalogen $!");
#
Dette er katalogen der alle data vil bli lagret
# om brukeren.
Soutput = ny CGI;
if (ikke $output->param) (
skriv ut $output->header,
$output->start_html("Min ordbok");
skriv ut "HTML;