Mailer eUczelni[edit] ¶
W ramach projektu eUczelnia powstał system umożliwiający wysyłanie wiadomości email.
JSON-RPC vs XML-RPC[edit] ¶
System wysyłania poczty oraz inne API systemu eUczelnia oparte są o protokół typu RPC (Remote Procedure Call), a formatem wymiany danych są JSON (JSON-RPC) lub XML (XML-RPC).
Pierwotnie system używał protokołu XML-RPC, jednak ze względu na bardzo duży stosunek ilości danych przesyłanych do ilości informacji w nich zawartych preferowany będzie protokół JSON-RPC.
Z przeprowadzonych doświadczeń wynika, że struktura danych informacji o pracowników przesyłana w formacie JSON zajmuje około 2500 bajtów, podczas gdy te same informacje w postaci XML zajmują około 12000 bajtów. W innych przypadkach różnica wielkości pakietu danych jest też wielokrotna i wynosi 400-600% więcej w przypadku XML, z korzyścią dla formatu JSON.
metoda send_message[edit] ¶
W celu wysłania wiadomości e-mail należy wywołać metodę send_message, której sygnatura ma postać:
/** * Funkcja umożliwia wysłanie dowolnej liczby wiadomości email do różnych adresatów * @param (string) api_key // klucz aplikacji, na podstawie którego jest uwierzytelniona * @param (array (message)) messages // tablica wiadomości przedstawionych jako obiekty * // typu message do wysłania, każda wiadomość * // zawiera pola do, od, tytuł i treść, patrz poniżej * @return (response) response // obiekt, który jest zwracany po wykonaniu funkcji */ (response) send_message( (string) api_key, ( array ) messages ); |
tablica messages[edit] ¶
Wiadomości przekazywane są w postaci tablicy tablic typu message.
Dla każdej wiadomości można zdefiniować dodatkowe nagłówki w postaci pojedynczego stringa. Nagłówki oddzielone są od siebie znakami CRLF (\r\n) tytuł nagłówka od jego zawartości oddzielony jest znakiem ":".
Struktura tablicy message (array of arrays) jako obiektu typu JSON wygląda następująco:
[ [ to, // string pole do from, // string pole od subject, // string pole temat text, // string pole treść headers // string dodatkowe nagłówki, które będą dołączone do wiadomości (opcjonalnie) ], (...) // kolejne wiadomości (opcjonalnie) ] |
Wysyłanie żądania[edit] ¶
JSON-RPC[edit] ¶
W ramach testowania możliwości wykorzystania protokołu JSON-RPC stworzony został prosty klient JSONa wykorzystujący certyfikaty do utworzenia połączenia poprzez bibliotekę cURL do serwera. Kod biblioteki ma następującą postać:
<?php class JsonRpcFault extends Exception {} class JsonRPCClient { private $uri ; private $ca_cert ; private $usr_cert ; /** * Zwraca losowy id zapytania. */ private function generateId() { $chars = array_merge (range( 'a' , 'z' ), range(0, 9)); $id = '' ; for ( $c = 0; $c < 5; ++ $c ) $id .= $chars [mt_rand(0, count ( $chars ) - 1)]; return $id ; } /** * Ustawienie uri do połączenia * @param string $uri */ public function set_uri( $uri ) { $this ->uri = $uri ; } /** * Ustawia ścieżkę do certyfikatu CA * @param string $path */ public function set_ca_cert( $path ) { $this ->ca_cert = $path ; } /** * Ustawia ścieżkę do certyfikatu hosta * @param string $path */ public function set_usr_cert( $path ) { $this ->usr_cert = $path ; } /** * otwiera połączenie, wysyła zapytanie i zwraca odpowiedź * @param string $name * @param array $arguments * @throws JsonRpcFault */ public function use_method( $name , $arguments ) { $id = $this ->generateId(); $request = array ( 'jsonrpc' => '2.0' , 'method' => $name , 'params' => $arguments , 'id' => $id ); $jsonRequest = json_encode( $request ); $curl = curl_init( $this ->uri); curl_setopt( $curl , CURLOPT_RETURNTRANSFER, 1); curl_setopt( $curl , CURLOPT_POST, 1); curl_setopt( $curl , CURLOPT_HTTPHEADER, array ( 'Content-type' => 'application/json; charset=UTF-8' )); curl_setopt( $curl , CURLOPT_POSTFIELDS, $jsonRequest ); curl_setopt( $curl , CURLOPT_HEADER, 0); curl_setopt( $curl , CURLOPT_SSLCERT, $this ->usr_cert); curl_setopt( $curl , CURLOPT_CAINFO, $this ->ca_cert); curl_setopt( $curl , CURLOPT_SSL_VERIFYPEER, TRUE); curl_setopt( $curl , CURLOPT_VERBOSE, FALSE); $jsonResponse = curl_exec( $curl ); if ( $jsonResponse === false) throw new JsonRpcFault( 'curl_exec failed' , -32603); $response = json_decode( $jsonResponse ); if ( $response === null) throw new JsonRpcFault( 'JSON cannot be decoded' , -32603); if ( $response ->id != $id ) throw new JsonRpcFault( 'Mismatched JSON-RPC IDs' , -32603); if (property_exists( $response , 'error' )) throw new JsonRpcFault( $response ->error->message, $response ->error->code); else if (property_exists( $response , 'result' )) return $response ->result; else throw new JsonRpcFault( 'Invalid JSON-RPC response' , -32603); } } ?> |
Tak napisaną bibliotekę należy wykorzystywać zgodnie ze schematem:
<?php include ( 'lib/JsonRPCClient.php' ); $con = new JsonRPCClient(); $con ->set_usr_cert( '<path>' ); $con ->set_ca_cert( '<path>' ); print_r( $con ->use_method( 'TestAPI.echo' , array ( 'Hello world' ) )); ?> |
Metoda TestAPI.echo zwraca cały komunikat jej przesłany i może być wykorzystywana do testowania poprawności połączenia. Serwer kraken jest serwerem testowym Politechniki Gdańskiej. Zmienna text przetrzymuje parametry przekazywane do funkcji (w tym wypadku pojedyńczą zmienną).
Wewnętrzna struktura komunikatu używana przez bibliotekę znajduje się w lini 57 klienta i wygląda następująco:
$request = array( 'jsonrpc' => '2.0' , 'method' => $name, 'params' => $arguments, 'id' => $id ); |
Gdzie name to nazwa metody, arguments to tablica argumentów przekazana do metody.
Uzyskana w ten sposób odpwiedź od serwera będzie miała postać typu:
{ "jsonrpc" : "2.0" , "id" : 1, "result" : { obiekt odpowiedzi} } |
Przy czym funkcja echo nie zwraca obiektu w takiej postaci, a jedynie zwraca obiekt do niej przesłany.
Wysłanie żądania wysyłki maili na konkretny adres będzie miało taką postać
<?php include ( 'lib/JsonRPCClient.php' ); $con = new JsonRPCClient(); $con ->set_usr_cert( '<path>' ); $con ->set_ca_cert( '<path>' ); print_r( $con ->use_method( 'TestAPI.echo' , array ( 'API-Secret-key' , array ( array ( 'receiver@example.org' , 'sender@example.org' , 'Subject' , 'Hello world!' , 'AdditionalHeader: XML-RPC mail sender' ) ) ) )); ?> |
XML-RPC[edit] ¶
W wykorzystaniu protokołu XML-RPC wykorzystaliśmy istniejące funkcje, które ułatwiają jego obsluge z poziomu PHP. Przykładowe żądanie ma następującą strukturę (opis w kodzie PHP, obiekt messages również w notacji PHP):
$send_message_request = array ( 'API-Secret-key' , array ( array ( 'receiver@example.org' , 'sender@example.org' , 'Subject' , 'Hello world!' , 'AdditionalHeader: XML-RPC mail sender' ), ); |
Przy użyciu języka PHP żądanie można zakodować do struktury XML używając następującej instrukcji:
$request = xmlrpc_encode_request( "send_message" , $send_message_request )); |
Która po zwraca następujący kod XML:
<? xml version = "1.0" encoding = "iso-8859-1" ?> < methodCall > < methodName >send_message</ methodName > < params > < param > < value > < string >Secret-key</ string > </ value > </ param > < param > < value > < array > < data > < value > < array > < data > < value > < string >receiver@example.org</ string > </ value > < value > < string >sender@example.org</ string > </ value > < value > < string >Subject</ string > </ value > < value > < string >Hello world!</ string > </ value > < value > < string >AdditionalHeader: XML-RPC mail sender</ string > </ value > </ data > </ array > </ value > </ data > </ array > </ value > </ param > </ params > </ methodCall > |
Nastęnie należy tak stworzone zapytanie opakować w nagłówki oraz przesłać do serwera docelowego:
$context = stream_context_create( array ( 'http' => array ( 'method' => "POST" , 'header' => "Content-Type: text/xml\r\nUser-Agent: PHPRPC/1.0\r\n" , 'content' => $request ))); |
Odbieranie odpowiedzi[edit] ¶
Otrzymana od serwera odpowiedź informująca o powodzeniu zakolejkowania wiadomośći ma następującą postać: Uwaga: sukces oznacza tylko i wyłącznie zakolejkowanie wiadomości do wysłania. Nie wiemy (i nie będzie możliwe otrzymanie takiej informacji) czy adresat docelowy istnieje oraz czy i kiedy otrzymał wiadomość.
<? xml version = "1.0" encoding = "iso-8859-1" ?> < methodResponse > < params > < param > < value > < struct > < member > < name >status</ name > < value > < string >ok</ string > </ value > </ member > < member > < name >errors</ name > < value > < array > < data /> </ array > </ value > </ member > < member > < name >user_message</ name > < value > < string >1 message(s) successfully queued for sending.</ string > </ value > </ member > </ struct > </ value > </ param > </ params > </ methodResponse > |
W przypadku niepowodzenia autoryzacji (błędny klucz aplikacji) wiadomość ma następującą postać:
<? xml version = "1.0" encoding = "iso-8859-1" ?> < methodResponse > < params > < param > < value > < struct > < member > < name >status</ name > < value > < string >error</ string > </ value > </ member > < member > < name >error_code</ name > < value > < int >100</ int > </ value > </ member > < member > < name >user_message</ name > < value > < string >Error: Authorization failed.</ string > </ value > </ member > </ struct > </ value > </ param > </ params > </ methodResponse > |
W przypadku błędów w wiadomościach (błędny adres odbiorcy) odpowiedź wygląda następująco: Uwaga: w przypadku błędnej chociaż jednej wiadomości nie zostanie wysłana żadna wiadomość. Wszystkie muszą być poprawne, aby paczka wiadomości (wszystkie przesłane przez jedno wywołanie funkcji) zostały zakolejkowane.
<? xml version = "1.0" encoding = "iso-8859-1" ?> < methodResponse > < params > < param > < value > < struct > < member > < name >status</ name > < value > < string >error</ string > </ value > </ member > < member > < name >errors</ name > < value > < array > < data > < value > < struct > < member > < name >to</ name > < value > < string >wrong@mail@example.org</ string > </ value > </ member > < member > < name >from</ name > < value > < string >sender@example.org</ string > </ value > </ member > < member > < name >subject</ name > < value > < string >Subject</ string > </ value > </ member > < member > < name >message</ name > < value > < string >Hello world!</ string > </ value > </ member > < member > < name >extra_headers</ name > < value > < string >AdditionalHeader: XML-RPC mail sender</ string > </ value > </ member > < member > < name >error_code</ name > < value > < int >50</ int > </ value > </ member > < member > < name >user_message</ name > < value > < string >Error: Recepient address (wrong@mail@example.org) incorrect</ string > </ value > </ member > </ struct > </ value > </ data > </ array > </ value > </ member > < member > < name >user_message</ name > < value > < string >0 were correct</ string > </ value > </ member > </ struct > </ value > </ param > </ params > </ methodResponse > |
Ostatnią czynnością jest rozkodowania odpowiedzi otrzymanej z serwera i przetworzenie jej:
$response = xmlrpc_decode( $response_xml ); |
Odpowiedź w postaci JSON dla wiadomości wysłanych prawidłowo ma następującą postać:
{ "status" : "ok" , "errors" : [], "user_message" : "1 message(s) successfully queued for sending." } |
Analogicznie, dla niepoprawnego klucza aplikacji, generowana jest taka odpowiedź:
{ "status" : "error" , "error_code" : 100, "user_message" : "Error: Authorization failed." } |
Wynikowa postać JSON w przypadku błędnych wiadomości ma następującą strukturę:
{ "status" : "error" , "errors" : [ { "to" : "wrong@mail@example.org" , "from" : "sender@example.org" , "subject" : "Subject" , "message" : "Hello world!" , "extra_headers" : "AdditionalHeader: XML-RPC mail sender" , "error_code" : 50, "user_message" : "Error: Recepient address (wrong@mail@example.org) incorrect" } ], "user_message" : "0 were correct" } |
Metody[edit] ¶
send_message[edit] ¶
send_message( (String) api_key, ( array ) messages ); /** * Funkcja umożliwia wysłanie dowolnej liczby wiadomości email do różnych adresatów * @param api_key // klucz aplikacji, na podstawie którego jest uwierzytelniona * @param messages // tablica wiadomości do wysłania, każda wiadomość zawiera pola do, od, tytuł i treść * @return response // obiekt, który jest zwracany po wykonaniu funkcji */ |
Obiekty pomocnicze[edit] ¶
messages[edit] ¶
[ [ to // string pole do from // string pole od subject // string pole temat text // string pole treść headers // string dodatkowe nagłówki, które będą dołączone do wiadomości ] ] |
response[edit] ¶
{ status: "error" , // string error / ok errors: // array podaje status oraz zwraca błędne wiadomości { to : "bob@example.org" , // string from : "alice@example.org" , // string subject : "test" , // string text : "message body" , // string headers : "" , // string (opcjonalnie) error_code : 100, // int kod błędu user_message : "Error: Recepient address (wrong@email@example.com) incorrect" // string }, user_message: "2 message(s) were submitted successfully" // string podaje status poprawnych wiadomości (wysłane/ poprawne) } |