Hei,
Yritän hakea staattisella datalla kaukoliikenteen aikatauluja. Ongelmana on kuitenkin, että saan ainoastaan vastaukseksi "Palveluun ei juuri nyt saada yhteyttä.". Alapuolella on ohjelmakoodini. Useasta erilaisesta yrityksestä huolimatta, en ymmärrä, että miksi kyseinen ohjelmakoodi ei toimi. Ongelma voi piileä ohjelmakoodin logiikassa, cookieissa tai esimerkiksi headereissa?
<?php class Vr { function __construct() { print_r($this->getSearchResultData()); // Tulostaa: Palveluun ei juuri nyt saada yhteyttä. } private function getJourneySearchData() { $url = "https://shop.vr.fi/onlineshop/JourneySearch.do"; $header = array( "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding: gzip, deflate", "Accept-Language: fi-FI,fi;q=0.8,en-US;q=0.5,en;q=0.3", "Connection: keep-alive", "DNT: 1", "Host: shop.vr.fi", "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0" ); $params = array( 'adultAmount' => true, 'adultCode' => 84, 'basic.campaignCode' => '', 'basic.departureDate.date' => '31.10.2015', 'basic.departureDate.hours' => 17, 'basic.departureDate.mins' => 11, 'basic.fromStationVR' => 'Jyv%C3%A4skyl%C3%A4', 'basic.oneWay' => true, 'basic.outwardTimeSelection' => true, 'basic.passengerNumbers%5B0%5D.passengerAmount:' => 1, 'basic.passengerNumbers%5B0%5D.passengerType' => 84, 'basic.returnDate.date' => '', 'basic.returnDate.hours' => '', 'basic.returnDate.mins' => '', 'basic.toStationVR' => 'Tampere', 'fromStationID' => 'Jy', 'request_locale' => 'fi', 'returnDateTab1' => '', 'returnTimeTab1' => '', 'sitesFromTab1' => 'Jyv%C3%A4skyl%C3%A4', 'sitesToTab1' => 'Tampere', 'startDateTab1' => '31.10.2015', 'startTime00Tab1' => '17%3A11', 'tabIndex' => '', 'toStationID' => 'Tpe' ); $ch = curl_init(); // Poistetaan SSL -sertifikaatin tarkistus. curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, true); // Palautetaan HTTP headerit curl_setopt($ch, CURLOPT_POSTFIELDS, $params); curl_setopt($ch, CURLOPT_HTTPHEADER, $header); $data = curl_exec($ch); $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE); curl_close($ch); return $this->createCookie($data, $headerSize); } private function createCookie($data, $headerSize) { $header = substr($data, 0, $headerSize); $headerInArray = array_slice(explode(":", $header), 8); $newHeaderArray = array(); foreach ($headerInArray as $value) { $array = explode(";", $value); $newHeaderArray[] = $array[0] . "; "; } return $newHeaderArray[0].$newHeaderArray[1]; } private function getSearchResultData() { $cookie = $this->getJourneySearchData(); $url = "https://shop.vr.fi/onlineshop/SearchResultDomestic.do"; $header = array( "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding: gzip, deflate", "Accept-Language: fi-FI,fi;q=0.8,en-US;q=0.5,en;q=0.3", "Connection: keep-alive", "Cookie: {$cookie}", "DNT: 1", "Host: shop.vr.fi", "Referer: https://shop.vr.fi/onlineshop/JourneySearch.do", "Upgrade-Insecure-Requests: 1", "User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0" ); $ch = curl_init(); // Poistetaan SSL -sertifikaatin tarkistus. curl_setopt ($ch, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, true); // Palautetaan HTTP headerit curl_setopt($ch, CURLOPT_HTTPHEADER, $header); curl_setopt($ch, CURLOPT_POST, 0); curl_setopt($ch, CURLOPT_ENCODING, ""); // Puretaan encoding $data = curl_exec($ch); curl_close($ch); return $data; } } new Vr(); ?>
CORS ei näyttänyt olevan sallittu, joten jätin testaamisen sikseen.
Suosittelisin käyttämään avointa rajapintaa tähän tarkoitukseen, VR:n verkkokaupan sijaan.
Esimerkki: Jyväskylä -> Tampere 10.10.2015 REST API -kutsu
Kävin katsomassa kyseisen rajapinnan digitraffic.fi. Rajapinta on muuten toimiva, mutta sieltä ei saa hintoja ulos. Onko siis mitään mahdollisuutta hakea tietoa vr:n sivuilta, jos CORS ei ole sallittu?
Koodipätkäni toimii, jos menen tekemään vr:n sivuille matkahaun -> kopioin esim. firebugista JSESSIONID:n ja Prod_teema_LBSESSIONin, jotka sivusto luo -> sijoitan seuraavan rivin tilalle:
$cookie = $this->getJourneySearchData();
esimerkiksi:
$cookie = "JSESSIONID =fguAWWC2t6nKi-MMME3HcrRbKNYIF3cNRTKUo_qobwTNRomYxMLL!1039559983; Prod_teema_LBSESSION=rd4o00000000000000000000ffffc031c80bo30040;";
ja ajan sovelluksen -> kaikki ok. Ilmeisesti ei ole kuitenkaan sama asia, että yritin ensiksi hakea cookien PHP curlin avulla getJourneySearchData(), jonka jälkeen teen toisen haun getSearchResultData(), jossa kyseinen cookie on mukana? Ilmeisesti cookie on ehtinyt vanhentua tässä välissä?
Toiminta ei ole varmasti kiinni kirjastosta vaan muista seikoista, esimerkiksi otsikoista tai ajoituksesta (tai bugista koodissasi). Oletko tarkistanut, että cookie-muuttujan sisältö on oikea? Katso kehityskonsolista tarkemmin, mitä pyyntöjä selain tekee ja mitä otsikoita niihin sisältyy, ja täydennä omaa koodiasi vastaavasti. User-Agent täytyy joskus myös asettaa. Tarvittaessa lisää johonkin väliin ylimääräinen viive, jos VR:n sivu on niin hieno, ettei kahta pyyntöä voi tehdä liian lähekkäin.
Miksi sinulla edes on eväste muuttujassa? Mikset käytä valmista evästesäilöä?
Olen tarkistanut, että cookien sisältö näyttää oikealta (vr luo jokaisella pyynnöllä uuden cookien). User-Agent on asetettu. Koitin asettaa myös viivettä, mutta se ei auttanut. Onko sillä tässä tapauksessa väliä, että palautanko cookien muuttajaan vai lisäänkö sen setcookie(...)? Enkö joudu kuitenkin hakemaan cookien muuttujaan $cookie = $_COOKIE[...]? Voiko tässä olla sellainen virhe, että kun teen ensimmäisen pyynnön vr:n sivulle, niin vr palauttaa HTTP headerin, josta saan luotua cookien. Seuraavaan pyyntöön olen lisännyt tämän cookien (jonka juuri muodostin), mutta vr ei enään tunnista tällä seuraavalla pyynnöllä cookieta oikeaksi vaan vanhentuneeksi - syystä, että...?
Lue koodivinkki kirjautumisesta. Siinä näkyy, miten evästeitä käsitellään ja miten libcurl ylipäänsä toimii parhaiten: käytä samaa instanssia kaikkiin pyyntöihin, aseta kaikki asetukset vain kerran niille tarkoitetuilla parametreilla (äläkä suoraan otsikoina), ja erityisesti aseta CURLOPT_COOKIESESSION ja CURLOPT_COOKIEJAR, niin evästeet säilyvät automaattisesti pyyntöjen välillä.
Katsoin nyt ensimmäisen kerran koodiasi, ja ainakaan tuossa createCookie-touhussa ei ole järjen häivää. Ilmeisesti oletat, että Set-Cookie-otsikko olisi vastauksessa aina samassa kohdassa. Skriptisi voi hajota hetkenä minä hyvänsä. (Lisäksi koko vaiva on turha, kuten edellä totesin.)
Dokumentaation mukaan evästeitä pitäisi käsitellä asetuksilla, joissa on sana COOKIE. Tämä implikoi, että CURLOPT_HTTPHEADER ei välttämättä aseta evästeitä oikein. (En testannut.)
Query kirjoitti:
Onko sillä tässä tapauksessa väliä, että palautanko cookien muuttajaan vai lisäänkö sen setcookie(...)? Enkö joudu kuitenkin hakemaan cookien muuttujaan $cookie = $_COOKIE[...]?
Nyt sekoitat asioita pahasti. PHP-skriptisi ja kävijän väliset evästeet (setcookie, $_COOKIE) ovat täysin eri asia kuin ne, joita nyt yrität käsitellä. Kun nyt kysyit, asialla on todella paljonkin merkitystä: setcookie olisi täydellisen väärä ratkaisu tässä tilanteessa (ellet jostain syystä halua lähettää VR:n evästettä käyttäjälle). Lisäksi setcookie ei aseta evästettä $_COOKIE-taulukkoon, joten koodi ei edes toimisi.
Et myöskään voi syöttää haluamaasi sessiotunnistetta itse, vaan se on saatava palvelimelta. Mikäli syötät oman tunnisteen, ja sessiota ei palvelimella ole olemassa, saat virheen session vanhenemisesta.
Aihe on jo aika vanha, joten et voi enää vastata siihen.