python: read grpc proto request in interceptor - php

How can I read grpc proto request in interceptor with Python?
In PHP, I can read the $argument, it's what I need.
<?php
use Grpc\Interceptor;
class MyInterceptor extends Interceptor
{
public function interceptUnaryUnary($method,
$argument,
$deserialize,
array $metadata = [],
array $options = [],
$continuation)
{
// $argument is what I needto
return parent::interceptUnaryUnary($method, $argument, $deserialize, $metadata, $options, $continuation);
}
}

Unfortunately, gRPC Python doesn't have a complete server interceptor implemenataion that allow you to access request or servicer_context, but you may access to method string and invocation_metadata, for more detail check the history. If the semantic you want to achieve can be implemented in Python's metaclass or inheritance, please do so. If you would like to request for this feature, please post an issue in the GitHub repo grpc/grpc...
Here are some resources that might help you find the answer:
The documentation for Python interceptors
The design of Python interceptors
The example for Python interceptors
The example Python interceptor for server

I implemented a small grpc-interceptor package which provides a server interceptor that has access to the request and context. It uses the built in grpc.ServerInterceptor, and just simplifies the interface.
Here's a short snippet for how to use it:
class MyInterceptor(ServerInterceptor):
def intercept(self, method, request, context, method_name):
# Do fancy stuff here...
return method(request, context)
There are more complete examples in the documentation.
It has an MIT license, so you're free to use it however you like. If you'd rather not use the package, but want to see how it works, the code is here. It was adapted and simplified from an OpenTracing PR.

Related

How can I do a partial integration test (phpunit)?

I am working on an extension (app) of nextcloud (which is based on Symfony). I have a helper class to extract data from the request that is passed by the HTTP server to PHP. A much-reduced one could be something like this (to get the point here):
<?php
namespace OCA\Cookbook\Helpers;
class RequestHelper {
public function getJson(){
if($_SERVER['Request_Method' === 'PUT'){ // Notice the typos, should be REQUEST_METHOD
$raw = file_get_content('php://input');
return json_decode($raw, true);
} else { /* ... */ }
}
}
Now I want to test this code. Of course, I can do some unit testing and mock the $_SERVER variable. Potentially I would have to extarct the file_get_content into its own method and do a partial mock of that class. I get that. The question is: How much is this test worth?
If I just mimick the behavior of that class (white box testing) in my test cases I might even copy and paste the typo I intentionally included here. As this code is an MWE, real code might get more complex and should be compatible with different HTTP servers (like apache, nginx, lighttpd etc).
So, ideally, I would like to do some automated testing in my CI process that uses a real HTTP server with different versions/programs to see if the integration is working correctly. Welcome to integration testing.
I could now run the nextcloud server with my extension included in a test environment and test some real API endpoints. This is more like functional testing as everything is tested (server, NC core, my code and the DB):
phpunit <---> HTTP server <---> nextcloud core <---> extension code <---> DB
^
|
+--> RequestHelper
Apart from speed, I have to carefully take into account to test all possible paths through the class RequestHelper (device under test, DUT). This seems a bit brittle to me in the long run.
All I could think of is adding a simple endpoint only for testing the functionality of the DUT, something like a pure echo endpoint or so. For the production use, I do not feel comfortable having something like this laying around.
I am therefore looking for an integration test with a partial mock of the app (mocking the business logic + DB) to test the route between the HTTP server and my DUT. In other words, I want to test the integration of the HTTP server, nextcloud core, my controller, and the DUT above without any business logic of my app.
How can I realize such test cases?
Edit 1
As I found from the comments the problem statement was not so obviously clear, I try to explain a bit at the cost of the simplicity of the use-case.
There is the nextcloud core that can be seen as a framework from the perspective of the app. So, there can be controller classes that can be used as targets for URL/API endpoints. So for example /apps/cookbook/recipe/15 with a GET method will fetch the recipe with id 15. Similarly, with PUT there can be a JSON uploaded to update that recipe.
So, inside the corresponding controller the structure is like
class RecipeController extends Controller {
/* Here the PUT /apps/cookbook/recipe/{id} endpoint will be routed */
public function update($id){
$json = $this->requestHelper->getJson(); // Call to helper
// Here comes the business logic
// aka calls to other classes that will save and update the state
// and perform the DB operation
$this->service->doSomething($json);
// Return an answer if the operation terminated successfully
return JsonResponse(['state'=>'ok'], 200);
}
}
I want to test the getJson() method against different servers. Here I want to mock at least the $this->service->doSomething($json) to be a no-op. Ideally, I would like to spy into the resulting $json variable to test that exactly.
No doubt, in my test class it would be something like
class TestResponseHandler extends TestCase {
public function setUp() { /* Set up the http deamon as system service */}
public testGetJson() {
// Creat Guzzle client
$client = new Client([
'base_uri' => 'http://localhost:8080/apps/cookbook',
]);
// Run the API call
$headers = ...;
$body = ...;
$response = $client->put('recipe/15', 'PUT', $headers, $body);
// Check the response body
// ....
}
}
Now, I have two code interpreters running: Once, there is the one (A) that runs phpunit (and makes the HTTP request). Second, there is the one (B) associated with the HTTP server listening on localhost:8080.
As the code above with the call to getJson() is running inside a PHP interpreter (B) outside the phpunit instance I cannot mock directly as far as I understand. I would have to change the main app's code if I am not mistaken.
Of course, I could provide (more or less) useful data in the test function and let the service->doSomething() method do its job but then I am no longer testing only a subset of functions but I am doing functional or system testing. Also, this makes it harder to generate well-aimed test cases if all these side-effects need to be taken into account.

How do I make a guzzle response into a response to a request from a client?

I have an application running on webserver A. I have a second application running on webserver B. Both webservers require a login. What I need to do is have a request to webserver A pass through to webserver B and return a file to the client without having the client login to Webserver B. (In other words, webserver B will be invisible to the client and I will take care of the auth credentials with my request to B from A). The code below is built on a laravel framework, but I don't believe the answer needs to be laravel specific.
The code works but it is only returning the HEAD information of the file to the calling client. Not the file itself.
Any help will be greatly appreciated!
Controller:
public function getAudioFile(Request $request)
{
//This is the id we are looking to pull
$uid = $request->uniqueid;
$audioServices = new AudioServices();
return $audioServices->getWavFile($uid);
}
Service:
public function getWavFile(String $uniqueId)
{
$client = new GuzzleHttp\Client(['verify' => false]);
return $client->request('GET', $this->connectString.$uniqueId, ['auth' => ['username', 'password']]);
}
As mentioned by bishop you can use sink option from Guzzle to stream the response of a Guzzle request.
You can pass that stream to a response from your controller. I'm not sure if Laravel has built-in stream support, but the underlying symfony httpfoundation components do. An example of it's usage can be found in this tutorial.
If you prefer not to use the sink option from Guzzle you can also use the response itself as that implements PSR-7 stream objects.

Q&A: How to get POST variables with PHP on Alibaba Cloud Function Compute service

I played around with the PHP 7.2 runtime and HTTP trigger on Alibaba Cloud Function Compute. The basic example in the documentation is the following:
<? php
use RingCentral\Psr7\Response;
function handler($request, $context): Response{
/*
$body = $request->getBody()->getContents();
$queries = $request->getQueryParams();
$method = $request->getMethod();
$headers = $request->getHeaders();
$path = $request->getAttribute("path");
$requestURI = $request->getAttribute("requestURI");
$clientIP = $request->getAttribute("clientIP");
*/
return new Response(
200,
array(
"custom_header1" => "v1"
),
"hello world"
);
}
This works quite well. It's easy to get the query parameters from an URL. But the body content is only available in a whole string with
$request->getBody()->getContents();
Although the documentation says that the $request parameter follows the PSR-7 HTTP Message standard, it is not possible to use $request->getParsedBody() to deliver the values submitted by POST method. It didn't work as expected - the result remains empty.
The reason is the underlying technology. Alibaba Cloud Function Compute makes use of the event-driven React PHP library to handle the requests (you can check this by analyzing the $request object). So the $_POST array is empty and there is no "easy way to get POST data".
Luckily, Alibaba's Function Compute handler provides the body content by $request->getBody()->getContents(); as a string like
"bar=lala&foo=bar"
So a solution seems easiser than thought at the beginning, you can e.g. use PHP's own parse_str() function:
$data = [];
$body = $request->getBody()->getContents();
parse_str($body,$data);
If you place this snippet in the handler function, the POST variables are stored in the $data array and ready for further processing.
Hope that this helps somebody who asked the same questions than I. :-)
Kind regards,
Ralf
As you can see in the documentation you need to add a RequestBodyParserMiddleware as middleware to get a parsed PSR-7 request. It seems you didn't do that.
Also keep in mind that only the Content-Types: application/x-www-form-urlencoded and multipart/form-data are supported here. So make sure the client need to send these headers so the request can be parsed. If it's another Content-Type you need to use another middleware.
See: https://github.com/reactphp/http#requestbodyparsermiddleware for more information.
I hope this helps!
#legionth: I apologize that I didn't use the comment feature here, but my answer is too long. :-)
Thanks a lot for your comments - the usage of RequestBodyParserMiddleware is a great solution if you can control the server code. But in the context of Alibaba Cloud Function Compute service this seems not possible. I tried to find out more information about the invocation process - here are my results:
Function Compute makes use of the Docker image defined in https://github.com/aliyun/fc-docker/blob/master/php7.2/run/Dockerfile .
In the build process they download a PHP runtime environment from https://my-fc-testt.oss-cn-shanghai.aliyuncs.com/php7.2.tgz . (I didn't find this on GitHub, but the code is public downloadable.)
A shell script start_server.sh starts a PHP-CGI binary and runs a PHP script server.php.
In server.php a React\Http\Server is started by:
$server = new Server(function (ServerRequestInterface $request) {
[...]
});
[...]
$socket = new \React\Socket\Server(sprintf('0.0.0.0:%s', $port), $loop);
$server->listen($socket);
$loop->run();
As seen in the Function Compute documentation (& example of FC console), I can only use two functions:
/*
if you open the initializer feature, please implement the initializer function, as below:
*/
function initializer($context) {
}
and the handler function you can find in my first post.
Maybe Alibaba will extend the PHP runtime in future to make it possible to use a custom middleware, but currently I didn't find a way to do this.
Thanks again & kind regards,
Ralf

Creating a web service in PHP

I would like to create a web service in PHP which can be consumed by different consumers (Web page, Android device, iOS device).
I come from a Microsoft background so am confortable in how I would do it in C# etc. Ideally I would like to be able to provide a REST service which can send JSON.
Can you let me know how I can achieve this in PHP?
Thanks
Tariq
I developed a class that is the PHP native SoapServer class' REST equivalent.
You just include the RestServer.php file and then use it as follows.
class Hello
{
public static function sayHello($name)
{
return "Hello, " . $name;
}
}
$rest = new RestServer(Hello);
$rest->handle();
Then you can make calls from another language like this:
http://myserver.com/path/to/api?method=sayHello&name=World
(Note that it doesn't matter what order the params are provided in the query string. Also, the param key names as well as the method name are case-insensitive.)
Get it here.
I would suggest you go for Yii it is worth of learning. You can easily establish it in this.
Web Service. Yii provides CWebService and CWebServiceAction to simplify the work of implementing Web service in a Web application. Web service relies on SOAP as its foundation layer of the communication protocol stack.
Easiest way in PHP is to use GET/POST as data-in and echo as data-out.
Here's a sample:
<?php if(empty($_GET['method'])) die('no method specified');
switch($_GET['method']){
case 'add': {
if(empty($_GET['a']) || empty($_GET['b'])) die("Please provide two numbers. ");
if(!is_numeric($_GET['a']) || !is_numeric($_GET['b'])) die("Those aren't numbers, please provide numbers. ");
die(''.($_GET['a']+$_GET['b']));
break;
}
}
Save this as test.php and go to http://localhost/test.php?method=add&a=2&b=3 (or wherever your webserver is) and it should say 5.
PHP does have native support for a SOAP server ( The SoapServer class manual shows it) and I've found it pretty simple to use.
Creating a REST style API is pretty easy if you use a framework. I don't want to get into a debate about which framework is better but CakePHP also supports output as XML and I'm pretty sure others will as well.
If you're coming from a Microsoft background just be careful about thinking about "datasets". They are a very specific Microsoft thing and have been a curse of mine in the past. It's probably not going to be an issue for you, but you may want to just see the differences between Microsoft and open implementations.
And of course PHP has a native json_encode() function.
You can check out this nice RESTful server written for Codeigniter, RESTful server.
It does support XML, JSON, etc. responses, so I think this is your library.
There is even a nice tutorial for this on the Tutsplus network -
Working with RESTful Services in CodeIgniter
You can also try PHP REST Data Services https://github.com/chaturadilan/PHP-Data-Services
You can use any existing PHP framework like CodeIgniter or Symfony or CakePHP to build the webservices.
You can also use plain PHP like disscussed in this example

Read HTTP headers in Controller (Zend Framework)

Long story short:
I'm building a skeleton application for Zend Framework and I got to the part where I need to setup the api module. I'm using Zend_Rest_Controller for this job. All is ok up to this part where I need to get the HTTP headers in a controller to verify the api key.
On various tutorials I've read on the web the thing is done via a front controller plugin, but I need it to be more "plug and play" than that (checking each time the config of the application, deciding which module is the api and so on).
I tried what seemed most obvious $this->getRequest()->getHeaders() but doesn't seem to work, at least not for the HTTP headers where I'll be seding my api key. Neither the reponse object.
Can anyone help me with this one?
I found a way of doing this after all :)
On the preDispatch() method in your controller you can do the following:
public function preDispatch()
{
$request = new Zend_Controller_Request_Http();
$key = $request->getHeader('x-apikey');
}
It seems that Zend_Controller_Request_Http object gives you acces to the headers. More info on the Zend_Controller_Request_Http you can find here
As Bogdan said, you can find that information in the Zend_Controller_Request_HTTP class. It can be found in the controller itself by doing :
$this -> getFrontController() -> getRequest() -> getHeader('Content-Type');
Unfortunatly, you can't access all headers at once but what ZF does is just use apache_request_headers() function if available on the server to get them.

Categories