gRPC: How can I distinguish bi-streaming clients at server side? - grpc

In this tutorial and example code, a server can call onNext() method on every stream observer, which will broadcast messages to all clients bi-streaming with the server. But there is no method to identify which observer corresponds to which client. How can a server push a message to specific client instead of broadcasting?
According to this answer it is possible to map each observer if client id is provided by metadata. It seems const auto clientMetadata = context->client_metadata(); part does the trick, but I'm working with Java, not C++. Are there any Java equivalent for getting the metadata at server side?

The answer depends a bit on how the clients will be identified. If the initial request provided a handle (like a username, but not registered ahead-of-time), then you could just wait for the first onNext():
public StreamObserver<Chat.ChatMessage> chat(StreamObserver<Chat.ChatMessageFromServer> responseObserver) {
return new StreamObserver<Chat.ChatMessage>() {
#Override
public void onNext(Chat.ChatMessage value) {
String userHandle = value.getHandle();
// observers would now be a map, not a set
observers.put(userHandle, responseObserver);
...
Let's say instead that all users are logged in, and provide a token in the headers, like OAuth. Then you would use an interceptor to authenticate the user and Context to propagate it to the application, as in https://stackoverflow.com/a/40113309/4690866 .
public StreamObserver<Chat.ChatMessage> chat(StreamObserver<Chat.ChatMessageFromServer> responseObserver) {
// USER_IDENTITY is a Context.Key, also used by the interceptor
User user = USER_IDENTITY.get();
observers.put(user.getName(), responseObserver);
return new StreamObserver<Chat.ChatMessage>() {
...
The first one is easier/nicer when the identification only applies to this one RPC. The second one is easier/nicer when the identification applies to many RPCs.

Related

Ethereum Chainlink HTTP Get not pinging my HTTP endpoint

I am attempting to have my Ethereum smart contract connect to an external HTTP endpoint using Chainlink. Following along with Chainlink's documentation (https://docs.chain.link/docs/advanced-tutorial/) I deployed this contract onto the Rinkeby testnet.
pragma solidity ^0.8.7;
import "github.com/smartcontractkit/chainlink/blob/develop/contracts/src/v0.8/ChainlinkClient.sol";
// MyContract inherits the ChainlinkClient contract to gain the
// functionality of creating Chainlink requests
contract getHTTP is ChainlinkClient {
using Chainlink for Chainlink.Request;
bytes32 private thisDoesNotWork;
address private owner;
address private ORACLE_ADDRESS = 0x718Cc73722a2621De5F2f0Cb47A5180875f62D60;
bytes32 private JOBID = stringToBytes32("86b489ec4d84439c96181a8df7b22223");
string private url = "<myHTTPAddressAsString>";
// This endpoint URL is hard coded in my contract, and stored as a string (as in the example code).
// I control it and can have it reply with whatever I want, which might be an issue, returning data in a format that the oracle rejects
uint256 constant private ORACLE_PAYMENT = 100000000000000000;
constructor() public {
// Set the address for the LINK token for the network
setPublicChainlinkToken();
owner = msg.sender;
}
function requestBytes()
public
onlyOwner
{
Chainlink.Request memory req = buildChainlinkRequest(JOBID, address(this), this.fulfill.selector);
req.add("get", url);
sendChainlinkRequestTo(ORACLE_ADDRESS, req, ORACLE_PAYMENT);
}
function fulfill(bytes32 _requestId, bytes32 recVal)
public
recordChainlinkFulfillment(_requestId)
{
thisDoesNotWork = recVal;
}
function cancelRequest(
bytes32 _requestId,
uint256 _payment,
bytes4 _callbackFunctionId,
uint256 _expiration
)
public
onlyOwner
{
cancelChainlinkRequest(_requestId, _payment, _callbackFunctionId, _expiration);
}
// withdrawLink allows the owner to withdraw any extra LINK on the contract
function withdrawLink()
public
onlyOwner
{
LinkTokenInterface link = LinkTokenInterface(chainlinkTokenAddress());
require(link.transfer(msg.sender, link.balanceOf(address(this))), "Unable to transfer");
}
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
// A helper funciton to make the string a bytes32
function stringToBytes32(string memory source) private pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}
assembly { // solhint-disable-line no-inline-assembly
result := mload(add(source, 32))
}
}
}
I found a node on the Chainlink market (https://market.link/jobs/529c7194-c665-4b30-8d25-5321ea49d9cc) that is currently active on rinkeby (according to Etherscan it has been active within the past 3 days and presumably still working).
I deploy the contract and fund the contract with LINK. I call the requestBytes() function through remix and everything works as expected. Metamask pays the gas, the LINK is removed from my contract, I get a transaction hash, and no errors.
However, my endpoint never logs a request attempt, the oracle never lists a transaction on its Etherscan page, and my data is not present.
I have attempted to use other jobs from the Chainlink market with similar outcomes.
I have also attempted to use other HTTP endpoints, like the ones from the Chainlink examples, with similar outcomes, however I doubt this is the issue, since it appears the HTTP request is never even getting called (as referenced by the fact that my HTTP endpoint does not log the request)
Without an error message, and being new to Web3 dev, I am not sure where to start debugging. I found this comment on Github: https://github.com/smartcontractkit/documentation/issues/513 and implemented the suggestion here without luck.
I also found this: Chainlink - Job not being fulfilled but this was not helpful either.
My current considerations for where the error might be:
The oracles are whitelisted and reject my request outright. Have considered creating my own node but want to avoid if possible at this stage.
I have an type error in how I am formatting the request in my contract, like the example in the GitHub exchange I found and referenced above.
EDIT: I am also open to other options beyond Chainlink to connect my contract to an HTTP GET endpoint, if anyone has any suggestions. Thanks!
I've been working on something similar recently and would suggest you try using the kovan network and the oracle that chainlink has there. Even more specifically, I think it would be a good idea to confirm you can get it working using the api, oracle, and jobid listed in the example on that page you are following... here:
https://docs.chain.link/docs/advanced-tutorial/#contract-example
Once you get that example working, then you can modify it for your usage. The jobid in that tutorial is for returning a (multiplied) uint256... which, for your API, I think is not what you want as you are wanting bytes32 it sounds like... so when you try to use it with your API that returns bytes32 the jobid would be: 7401f318127148a894c00c292e486ffd as seen here:
https://docs.chain.link/docs/decentralized-oracles-ethereum-mainnet/
Another thing that might be your issue, is your api. You say you control what it returns... I think it might have to return a response in bytes format, like Patrick says in his response (and his comments on his response) here:
Get a string from any API using Chainlink Large Response Example
Hope this is helpful. If you cannot get the example in the chainlink docs to work, let me know.

Send to certain connections only Spring Websockets

I am using grails/groovy so excuse the odd syntax, i am also new to using websockets so please let me know if i am going about this in the wrong way:
Using spring websockets i am able to send messages to certain subscribed users via
SimpMessagingTemplate brokerMessagingTemplate
users.each {
brokerMessagingTemplate.convertAndSendToUser(it.id,"/topic/path",data)
}
However, i want to send messages only to subscribed users have passed to the server a certain value/id over and above their user id. A connection is initialised on wepage load so i figured perhaps that i could add a STOMP header value which passes this information to the server, and the server only sends messages to connections which match this.
var socket = new SockJS("/url/stomp");
var client = Stomp.over(socket);
var headers = {'additionalId': additionalId};
client.connect({}, function() {
client.subscribe("/user/topic/path", function (data) {
}, headers);
firstly, i dont know whether adding a header value is the right way to do this, and secondly im not sure how to make the SimpMessagingTemplate send to those that have specifically provided the additional Id in the header.
Instead of using a header you can use DestinationVariable as so:
brokerMessagingTemplate.convertAndSend("/topic/something.${additionalId}".toString(), data)
and use
#MessageMapping("/something.{additionalId}")
protected String chatMessage(#DestinationVariable String additionalId, Principal principal, String data) { ... }
Additionally you may want to limit who subscribe to a specific /something.{additionalId} by implementing a TopicSubscriptionInterceptor() where you can validate the Principal

sending messages to single clients NOT identified by name (Identity)

I know similar questions have been asked before, but here goes
I have an ASP.NET app that serves images to connected clients. All clients are connected via owin with username and password and there could multiple clients connected with the same username and password. However, each client may need to be served with unique images. This means that I need to use a unique "hub ID" to serve each image.
The problem comes from retrieving this "hub ID" on the GetUserID method of the CustomUserProvider class. The IRequest parameter doesn't provide me with enough information to uniquely identify the connection. If I can get (which I can't (??)) to the Session state of the page then problem solved.
Has anyone got any ideas. I'm thinking of perhaps using the url - which I can make unique for each connection....
(Does anyone know how to get the original url of the page in the GetUserID)
I solved this as follows. I append a unique id on the URL. Then in the GetUserID of the CustomUserProvider
public string GetUserId(IRequest request)
{
string id = "";
try
{
HttpContextBase requestContext = request.Environment[typeof(HttpContextBase).FullName] as HttpContextBase;
string url = requestContext.Request.UrlReferrer.AbsoluteUri;
var parsedQuery = HttpUtility.ParseQueryString(url);
id = parsedQuery["HUBID"];
}
catch { }
return id;
This HUBID is the one referenced in the code behind:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<Hubs.MimicHub>();
hubContext.Clients.User(HubID).addImage(MimicImage,
ImageWidth, ImageHeight
);
Every Signalr connection (client) will have its own ConnectionId.
You could use this ID to Identify the same user foreach connection.
You can receive this unique connectionId:
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
}
more info:
http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections
Please see amended question. There may have been a better one, but this works perfectly.

Winform calling ASP.NET Web API with Claims

I have a Winform client that we are slowing changing inline SQL data calls into ASP.NET Web API calls. We currently use the WindowsPrincipal.IsInRole check in the Winform client to determine if the user can run the SQL data calls. We would like to move into a Claims type setup where both the Winform client and the Web API can check the roles "claims" of a user.
I can't seem to find any "good" articles on how to get a Winform client to (1. Pass the claim to the service) and (2. Use a claim check inside the Winform client like the IsInRole). Any help or push in the right direction would be great.
--EDIT
So I used this article http://zamd.net/2012/05/04/claim-based-security-for-asp-net-web-apis-using-dotnetopenauth/ as a sample on getting a token back from the server but the article does not show how to get the claims identity out of the http client. Any idea how to get the claims identity out of the http client?
While I haven't tested this code, hopefully it will get you moving in the right direction.
I believe to answer your question you do this in your ClaimsAuthenticationManager where upon validating the token received from the server you set the Thread.CurrentPrincipal -- the same way you do on the web side without setting the HttpContext.Current.User principal.
Again this isn't tested but I think it would look something like this...
In my Token Validator I have the following code:
public static ClaimsPrincipal ValidateToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(new JwtSecurityToken(token),
Constants.TokenValidationParameters);
return FederatedAuthentication.FederationConfiguration
.IdentityConfiguration
.ClaimsAuthenticationManager.Authenticate(token, claimsPrincipal);
}
public static string GetToken(string username, string password)
{
OAuth2Client client = Constants.OAuth2Client;
AccessTokenResponse response = client.RequestAccessTokenUserName(username.ToLower(), password,
Constants.AllowedAudience);
return response.AccessToken;
}
Within my ClaimsAuthenticationManager I have modified the following code as you don't want to set the HttpContext in a non web environment:
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
{
if (!incomingPrincipal.Identity.IsAuthenticated)
{
return base.Authenticate(resourceName, incomingPrincipal);
}
/* HttpContext.Current.User = */ Thread.CurrentPrincipal = incomingPrincipal;
return incomingPrincipal;
}
I believe you then just have to set the appropriate keys in the app.config, specifically the system.identityModel => identityConfiguration => claimsAuthenticationManager
Once the thread you are running on has the "Authenticated Principal" you should be able to call the ClaimsPrincipal.Current.HasClaim() or your higher level Authorization.CheckAccess() function to validate sections of your WinForm logic.
Hope this helps :)

Unable to broadcast to single connection using Atmosphere runtime

I am using Atmosphere runtime 0.6 Snapshot. Tomcat 7 is logging correctly that I am using the Http11 Nio connector and there is no warning that BlockingIO will be used.
I am trying to send messages to three kinds of channels.
Global Broadcaster - broadcast to all suspended resources. (All)
Broadcast to a particular resource (say, Partner)
Broadcast to current resource (Self)
When a login action occurs, what all do I have to store in session in order to achieve this kind of broadcasting?
Some details of my code are as follows:
My Handler implements AtmosphereHandler
In the constructor, I instantiate the globalBroadcaster as follows:
globalBroadcaster = new DefaultBroadcaster();
On login,
resource.getAtmosphereConfig().getServletContext().setAttribute(name, selfBroadcaster);
where name is the user name from request parameter and selfBroadcaster is a new instance of DefaultBroadcaster.
Here is the code for sendMessageToPartner,
private synchronized void sendMessageToPartner(Broadcaster selfBroadcaster,
AtmosphereResource<HttpServletRequest, HttpServletResponse> resource,String name, String message) {
// this gives the partner's name
String partner= (String) resource.getAtmosphereConfig().getServletContext().getAttribute(name + PARTNER_NAME_TOKEN);
// get partner's broadcaster
Broadcaster outsiderBroadcaster = (Broadcaster) resource
.getAtmosphereConfig().getServletContext()
.getAttribute(partner);
if (outsiderBroadcaster == null) {
sendMessage(selfBroadcaster, "Invalid user " + partner);
return;
}
// broadcast to partner
outsiderBroadcaster.broadcast(" **" + message);
I hope I have given all the required information. I can provide more information if required.
The problem is, the global message gets sent. When message to partner is sent, sometimes it gets blocked, the message is not received in the client at all. This happens consistently after 3-4 messages.
Is there some threading problem? What am I doing wrong?
I hope somebody helps me out with this.
Ok, I figured out how this can be achieved with Atmosphere runtime.
First, I upgraded to 0.7 SNAPSHOT, but I think the same logic would work with 0.6 as well.
So, to create a broadcaster for a single user:
In GET request,
// Use one Broadcaster per AtmosphereResource
try {
atmoResource.setBroadcaster(BroadcasterFactory.getDefault().get());
} catch (Throwable t) {
throw new IOException(t);
}
// Create a Broadcaster based on this session id.
selfBroadcaster = atmoResource.getBroadcaster();
// add to the selfBroadcaster
selfBroadcaster.addAtmosphereResource(atmoResource);
atmoResource.suspend();
When login action is invoked,
//Get this broadcaster from session and add it to BroadcasterFactory.
Broadcaster selfBroadcaster = (Broadcaster) session.getAttribute(sessionId);
BroadcasterFactory.getDefault().add(selfBroadcaster, name);
Now the global broadcaster. The logic here is, you create a broadcaster from the first resource and then add each resource as they log in.
Broadcaster globalBroadcaster;
globalBroadcaster = BroadcasterFactory.getDefault().lookup(DefaultBroadcaster.class, GLOBAL_TOKEN, false);
if (globalBroadcaster == null) {
globalBroadcaster = selfBroadcaster;
} else {
BroadcasterFactory.getDefault().remove(
globalBroadcaster, GLOBAL_TOKEN);
AtmosphereResource r = (AtmosphereResource) session
.getAttribute("atmoResource");
globalBroadcaster.addAtmosphereResource(r);
}
BroadcasterFactory.getDefault().add(globalBroadcaster,
GLOBAL_TOKEN);
Finally, you can broadcast to Single connection or Globally to all connections as follows:
// Single Connection/Session
Broadcaster singleBroadcaster= BroadcasterFactory.getDefault().lookup(
DefaultBroadcaster.class, name);
singleBroadcaster.broadcast("Only for you");
// Global
Broadcaster globalBroadcaster = BroadcasterFactory.getDefault().lookup(DefaultBroadcaster.class,GLOBAL_TOKEN, false);
globalBroadcaster.broadcast("Global message to all");
To send message to partner, just lookup the broadcaster for the partner and do the same as above for single connection.
Hope this helps someone who tries to achieve the same.
There may be better ways of doing this.
I think I will have to use this approach until someone suggests a better solution.

Resources