Wysyłanie pliku XML do API

0

Hej,

Zgłaszam się do Was dzisiaj z uprzejmą prośbą o nakierowanie mnie, co robię źle przy próbie wysyłania zapytania do API. Tak, wiem, na necie jest pełno poradników dotyczących JS / AJAX / JQuery, jednakże zazwyczaj one dotyczą odczytywania danych z API, a moim celem jest stworzenie funkcji przesyłającej dane do API jednej z usług. Poniżej zamieszczam kod:

Oczywiście celowo podmieniłem API Key, także to nie ono jest problemem, sam klucz jest w pełni okej. ;)

<script>
function  UserAction(){
	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function() {
		if (this.readyState == 4 && this.status == 200) {
		   // Typical action to be performed when the document is ready:
			var response = xhr.responseText;
			console.log("ok"+response);
		}
	};
	xhr.open('POST','https://api5.esv2.com/v2/Api/Subscribers/', true);
	
	xhr.send("POST https://api5.esv2.com/v2/Api/Subscribers/ HTTP/1.1  Content-Type: text/xml  <ApiRequest xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\"> <ApiKey>KEY</ApiKey><Data xsi:type=\"Subscriber\"><Mode>AddAndUpdate</Mode><Force>true</Force><ListId>1</ListId><Email>[email protected]</Email><Firstname>John</Firstname><Lastname>Smith</Lastname><TrackingCode>123</TrackingCode><Vendor>xyz</Vendor><Ip>11.22.33.44</Ip><Properties><Property><Id>5</Id><Value xsi:type=\"xs:string\">student</Value></Property><Property><Id>3</Id><Value xsi:type=\"xs:dateTime\">1985-03-12</Value></Property></Properties></Data></ApiRequest>");

}
0

Może spróbuj dodania przed xhr.send jeszcze:

xhr.setRequestHeader("Content-type", "co tam potrzebujesz");

Poza tym prześlij sobie te dane do własnej strony lokalnej, odczytaj je i zobacz, co zostało przesłane.

Poza tym mam wątpliwości, czy ten format wysyłanych danych:

 xhr.send("POST https://api5.esv2.com/v2/Api/Subscribers/ HTTP/1.1  Content-Type: text/xml  <ApiRequest. (...)>

jest poprawny. Adres i content type masz podać wcześniej, no chyba, że to API wymaga powtórzenia tej informacji również w przesyłanych danych, ale myślę, że wątpię.

Poza tym nie napisałeś w sumie, na czym polega błąd.

0

@Freja Draco: Hmm header ustawiem, niesety nie pomogło. :/ sam błąd się tak naprawde nie wyświetla, wygląda to tak, jakby zapytanie się w ogóle nie wysyłało (?). Dodam jeszcze, że wcześniej jak sobie próbowałem pobrać dane z API za pomocą metody GET to poszło to bez większych problemów

0

Czyli nie dostajesz żadnej odpowiedzi z serwera? Nawet informacji o błędzie?

Spróbuj to może przetestować jakoś inaczej. Wyślij dane PHP-em i zobacz, czy dojdą.

0

@Freja Draco:
Dokładnie tak, po prostu jakby się nie działo nic, ewentualnie jak próbowałem teraz wyświetlać xhr.responseXML, to otrzymywałem po prostu w konsoli wartość null.
Hmm przesłałem do pliku php i wyświetliłem za pomocą takiego kodu:

<?php 
echo '<pre>';
  print_r ($_POST );
  $postData = trim(file_get_contents('php://input'));
  print_r($postData);
echo '</pre>';
?>

W logu konsoli przeglądarki otrzymałem to:

ok<pre>Array
(
)
<ApiRequest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <ApiKey>api key</ApiKey><Data xsi:type="Subscriber"><Mode>AddAndUpdate</Mode><Force>true</Force><ListId>1</ListId><Email>[email protected]</Email><Firstname>John</Firstname><Lastname>Smith</Lastname><TrackingCode>123</TrackingCode><Vendor>xyz</Vendor><Ip>11.22.33.44</Ip><Properties><Property><Id>2</Id><Value xsi:type="xs:string">student</Value></Property><Property><Id>3</Id><Value xsi:type="xs:dateTime">1985-03-12</Value></Property></Properties></Data></ApiRequest></pre>
0

@kunegundek: oczywiście API key podmienilem, jest tam normalna jego wartość :)

0

@Freja Draco: Spróbowałem z innej przeglądarki i w konsoli wyskoczyło to:

Zablokowano żądanie do zasobu innego pochodzenia: zasady „Same Origin Policy” nie pozwalają wczytywać zdalnych zasobów z „https://api5.esv2.com/v2/Api/Subscribers/” (niedozwolone zdalne przekierowanie żądania CORS).

0

Miałam kiedyś podobny problem, warunek:
if (this.readyState == 4 && this.status == 200)
zachodził, ale przy próbie przesłania AJAXem danych na inny serwer, przeglądarka nie miała dostępu do odpowiedzi serwera.

"Rozwiązałam" to tak, że wysyłałam dane na pałę, bez dostępu do komunikatu zwrotnego.

0

@Freja Draco: U mnie niestety też nie pomaga. :( Nie mam już kompletnie pomysłu, jak to ogarnąć.

0

@szatkus: Dokładnie to dwa błędy wyskakują:

Access to XMLHttpRequest at 'https://api5.esv2.com/v2/Api/Subscribers/' from origin 'https://sklep.kfd.pl' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
sub.php:72 POST https://api5.esv2.com/v2/Api/Subscribers/ net::ERR_FAILED

0

Przecież masz napisane że to CORS. CORS jest mechanizmem który ogranicza możliwość przeglądarek do wysyłania requestów na konkretne API jeżeli nie pochodzą z konkretnego źródła.
Jesteś pewien że administrator API pozwolił Ci na wykonywanie requestów do tego endpointu?

0
Lucassith napisał(a):

Przecież masz napisane że to CORS. CORS jest mechanizmem który ogranicza możliwość przeglądarek do wysyłania requestów na konkretne API

Ale w tej sytuacji to jest bardziej złożone. Możesz wysłać dane JS-em na obcy serwer, ale przeglądarka nie przyjmie komunikatu odpowiedzi, chociaż odbierze sam fakt zmiany stanu na "wysłane" i chociaż same dane dojdą na obcy serwer.

0

@Freja Draco: Skąd taki pomysł? CORS to preflight z verbem OPTIONS - w razie błędu, przeglądarka nie wyśle prawdziwego requestu GET/POST.

To co napisałaś to kompletna bzdura. Przecież to byłoby wręcz absurdalne z punktu widzenia feature którym jest CORS, żeby w razie błędu i tak wysyłać request...

Opisane jest to w tej książce:

Figure 4.17. If the server rejects a preflight request, the browser returns an error to the client without ever sending the actual request.

https://livebook.manning.com/book/cors-in-action/chapter-4/143
Chapter 4.4.4.

Dla pewności sprawdziłem w3c i specyfikację CORS - w razie błędu zwracasz network error.

https://fetch.spec.whatwg.org/#cors-preflight-fetch punkt #8

Nigdy w życiu taki request z danymi nie dojdzie do serwera.

0
Lucassith napisał(a):

@Freja Draco: Skąd taki pomysł? CORS to preflight z verbem OPTIONS - w razie błędu, przeglądarka nie wyśle prawdziwego requestu GET/POST.

To co napisałaś to kompletna bzdura. Przecież to byłoby wręcz absurdalne z punktu widzenia feature którym jest CORS, żeby w razie błędu i tak wysyłać request...

Dla pewności zrobiłam właśnie testy.

Dwa lokalne serwery

  • 1 ma w konfiguracji ustawione Header set Access-Control-Allow-Origin "*"
  • 2 nie posiada takiego zapisu

Dane wysyłane JS-em:

    xhttp.open("POST", popup_adres_wysylania_post, true);
    xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xhttp.send("phone="+nr);

 

Na obu serwerach taki sam skrypt, czytający wartość z $_POST i zapisujący przekazaną wartość do pliku.

  • sprawdziłam, na obu serwerach nowe dane są dopisywane do plików.

Przy wystąpieniu xhttp.onreadystatechange
dostaję dla właściwości:
this.readyState, this.status, this.statusText, this.responseText

Pierwszy serwer:
1 0
2 200 OK
3 200 OK mój-komunikat-zwrotny
4 200 OK mój-komunikat-zwrotny

Drugi serwer:
1 0
2 0
4 0

Zatem, jest tak jak mówiłam:

  • możesz wysłać JS-em dane na obcy serwer niezależnie od CORS,
  • CORS może blokować treść komunikatów zwrotnych.

Więc się nie dowiesz, czy wszystko poszło prawidłowo, ale możesz przyjąć na pałę, że doszło i jeśli przetestujesz sobie od strony serwera, że te dane tam jednak dochodzą, to takie trochę kulawe rozwiązanie będzie jednak działać.

0

@Freja Draco:
Niestety jesteś w błędzie. CORS jest mocno naiwnym zabezpieczeniem, jego celem są użytkownicy casuale którzy korzystają z przeglądarki.
Use-case jest następujący -> tworzysz sobie API które coś oblicza, wystawiasz własną stronę i udostępniasz swoją logikę po API.

Teraz - ktoś inny tworzy podobną stronę i korzysta z Twojego API. Użytkownicy zamiast wchodzić na Twoją stronę, wchodzą na czyjąś. CORS w tym momencie pozwala na wyłączenie możliwości wysyłania requestów z innego origin niż Twoja strona www.

Naiwność CORS polega na tym, że jego ochrona działa tylko w przeglądarce która poprawnie implementuje standard CORS czyli preflight. Jednakże tak jak napisałem, casualowy użytkownik przeglądarki nie wyłączy preflight i dzięki temu czyjaś strona będzie failowała od strony user experience.

To co Ty robisz, to co pokazujesz, to naruszenie standardu CORS. Jasna sprawa, że człowiek który potrafi programować, może od razu wysłać request POST i mieć głęboko cały ten CORS razem z preflight i requestem OPTIONS (dokładnie to zrobiłaś). Natomiast tu chodzi o osoby które wchodzą na Twoją stronę i próbują wykonać jakiś request, wtedy przeglądarka automatycznie go blokuje. To jest zabezpieczenia anty-mob, aby działało, to klient a nie serwer muszą spełniać konkretne założenia.

Kolega @kunegundek wysłał log gdzie jasno jest napisane, że jego request sfailował na preflight czyli nigdy nie dojdzie do requestu POST:

preflight request doesn't pass access control check:

Specjalnie dla Ciebie stworzyłem stronę która pokazuje działanie CORS:
Są 2 URL:
Tutaj masz historię requestów:
https://cors.3lancers.dev/history

Tutaj jest strona z której możesz wysłać request:
https://corstest.3lancers.dev/

na tej drugiej masz 2 przyciski,
pierwszy wysyła request na https://cors.3lancers.dev/cors który ma poprawnie zabezpieczony origin,
drugi przycisk wysyła request na https://cors.3lancers.dev/no-cors

Sprawdzając w konsoli pod zakładką Network zobaczysz, że oba requesty poprawnie się wysyłają, ale przycisk call /cors umrze na preflight.
Natomiast jak sama zrobisz sobie request na /cors bezpośrednio na POST bez sprawdzania preflight to taki request się pojawi w historii.

Ale tak jak wyżej napisałem, CORS to zabezpieczenie przeglądarki, call bezpośrednio na POST zawsze zadziała.

0

@kunegundek
Rozwiązanie Twojego problemu jest takie, że nie możesz wysyłać requestów bezpośrednio z przeglądarki, bo ona owrapuje Twój request w preflight i request nigdy nie dojdzie.
Masz 2 wyjścia:
a). Stworzyć aplikację która komunikuje się z tym API o którym pisałeś bez udziału przeglądarki, napisać ją na przykład w NodeJS i tam sobie obsługiwać requesty.
b). Stworzyć aplikację która jest proxy, udostępnia jakiś port i forwarduje request na API z CORSem. Wtedy możesz ignorować cały CORS.

0

Dobra, chyba już wiem o co chodzi.

kunegundek napisał(a):

Hmm header ustawiem, niesety nie pomogło. :/

I myślę, że tutaj popełniłeś błąd. To API, które używasz jest delikatnie mówiąc ujowe. Dla preflight request zwraca... 404. Ale na szczęście to nie jest jedyna niedorobiona rzecz w tym API, bo wygląda na to, że nie potrzebuje tego nagłówka do szczęścia (a przynajmniej na to wygląda, bo dostaję "Supplied API key is invalid." czyli musiał sparsować tego xmla). Wtedy to jest traktowane jako simple request i nie leci zapytanie OPTIONS. Takie zapytanie też się nie wywali, bo w nagłówkach zwraca access-control-allow-origin: * czyli to jest ładny i poprawny CORS z punktu widzenia przeglądarki.

Możesz to potraktować jako opcję c.

0
Lucassith napisał(a):

Naiwność CORS polega na tym, że jego ochrona działa tylko w przeglądarce która poprawnie implementuje standard CORS czyli preflight. Jednakże tak jak napisałem, casualowy użytkownik przeglądarki nie wyłączy preflight i dzięki temu czyjaś strona będzie failowała od strony user experience.

Przyznam, że się trochę w tym pogubiłam, a nie chce mi się jakoś głębiej studiować, więc zakładam, ze wiesz więcej.

Niemniej nie wyłączałam CORS w przeglądarce, spróbowałam nawet wywołać ten skrypt w innej i co prawda wyrzuciła komunikat:

XMLHttpRequest cannot load http://ajax-bez-pozwolen.loc/czytaj_post_zapisz_post.php.
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

Ale dane też doszły i zostały zapisane.

Kolega @kunegundek wysłał log gdzie jasno jest napisane, że jego request sfailował na preflight czyli nigdy nie dojdzie do requestu POST:

preflight request doesn't pass access control check:

Widzę, że u niego w komunikacie pojawia się: Redirect is not allowed for a preflight request

Access to XMLHttpRequest at 'https://api5.esv2.com/v2/Api/Subscribers/' from origin 'https://sklep.kfd.pl' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
sub.php:72 POST https://api5.esv2.com/v2/Api/Subscribers/ net::ERR_FAILED

Więc może z tego wynika różnica w działaniu.

1 użytkowników online, w tym zalogowanych: 0, gości: 1