When the same app acts as a Bonjour-enabled service and client at the same time, browsing for self-like services while listening on a socket, what's a good way to exclude self from service search results?
In your NSNetServiceBrowserDelegate you can just ask the incoming service if it is the same as the one you have published:
-(void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser didFindService:(NSNetService *)netService moreComing:(BOOL)moreComing
{
if ([netService isEqual:self.publishedNetService])
return;
…
}
In the server part, when I register my service with Bonjour, I generate a 10 character random alphanumeric string - a cookie. I make it a part of the advertised service name, as provided in call to [NSNetService initWithDomain:type:name:port:]. The resulting name is something like "MyApp on Joe's iPhone\txYbG56HjaE". The part before the tab character is for display, the part after is the cookie. I then store the cookie is a globally visible variable. Since server initialization takes place on app startup, the cookie value is available early on.
In the service discovery part, when I find a service, I check its name; if the cookie is the same as the stored global one, I skip this service. The idea is that other running instances of the program would have different cookie values, because randomness.
Naturally, when displaying discovered service names in the UI, I skip the part after the tab character.
Related
I am switching to offline firs app from my already made online app.
This is how online works:
I have VIPER interactor which requests data from service. Service knows how to request data from API layer. So then I have callbacks with result or error and process it in integrator and then update local storage if needed. Nothing super hard here.
So all elements, Interactor, Service and API are single responsibility objects and do only one task:
Interactor handles if blocks logic to handle result or error and trigger presenter to display data
Service calls for API
API calls Alomofire to do rest of work with requests.
So now in offline first app I added RequestService where I store all my requests and then send it using Timer and in case connection is online.
So now I need to overload single responsibility somewhere to check next things.
So first off all I need to check reachability:
if noConnection() {
loadLocalDataToShow()
}
Next I need to make sure all requests has been sent:
if requestsService.pendingRequests > 0 {
loadLocalDataToShow()
}
So there are two approaches as I thinks:
Make global check. Like providing API layer to do these checks for me and return some enum Result(localData) or Result(serverData) after Alamofire returned for me with result or if no connection.
Or second one make interactor to do these checks like this:
func getData(completion ...) {
Service.getData() result in {
if requestService.pendingRequests > 0 {
completion(loadLocalData())
}
if result.connectionError {
completion(loadLocalData())
}
completion(result) //result it's returned data like array of some entities or whatever requested from the API and fetched directly from server via JSON
}
}
so now we will have kind of all of the same check for all interactions which requested data, but seems we have not broken single responsibility or maybe I am on a wrong way?
TL;DR: IMHO, I'd go with second one, and it won't be breaking SRP.
VIPER Interactors tend to have multiple services, so it's perfectly fine to have something like OnlineRequestService and OfflineRequestService in the interactor, and act accordingly.
Hence, you won't be breaking SRP if you decide which data/service to use in the interactor itself.
To elaborate more, let's say that there was an initial requirement for user, that they may use the app online/offline. How would you plan your architecture? I would create the services mentioned in the upper part, and let the interactor decide which service to use.
Interactor in VIPER is responsible for making requests, and it may go with different Services, such as CoreDataService, NetworkService, even UserDefaultsService. We cannot say that interactor is doing only one task, but it doesn't necessarily mean that it has more than one responsibility. Its responsibility is to take care of the flow between the data and the presenter, and if there needs to be a decision between which data (online/offline) to use, it would reside in the interactor's responsibility.
If it still doesn't feel right, you may create an additional interactor, but who/what would decide which interactor to use?
Hope this helps.
Spring AMQP custom message correlation using an identifier generated by the app for the outbound gateway using spring integration.
We have a requirement where we need to correlate messages for outbound gateway with an id generated by app, in which actual processing of the messages would happen in external system and the response for the request will come as post from the external system, so we cannot rely on amqp_correlation data.
If you provide the steps for this that will be great.
Solution Tried
Set the correlation key in the rabbit template
Create Message of type AMQP, set the header name with the correleation key set in the template with some generated value
Provide header-mapper in the AMQP outbound gateway for the custom header name
Result
Rabbit template was able to map with the custom header, However it generates its own value, Not using the value that was set in the request/reply messages
Please open a new feature JIRA Issue for this.
Bear in mind it will be your responsibility to ensure the correlationId is unique.
You might be able to work around it by subclassing the template and overriding sendToRabbit; and set up the correlationId there; you would have to save off the template's correlationId (ideally in the message in a different header, but perhaps in a Map) and have the server return that header too.
protected void sendToRabbit(Channel channel, String exchange, String routingKey, boolean mandatory,
Message message) throws IOException {
// fix up properties
super.doSend(...);
}
You would also have to override onMessage() to restore the proper correlationId for the inbound request.
I'm using ADALiOS v3.0.0-pre.2 to connect to Azure AD B2C and authorize a given user. I successfully obtain an accessToken for the user, who gets prompted with UI to login in the process. I use the acquireTokenWithScopes method on ADAuthenticationContext instance to do so.
Somewhere down the line, I want to make sure that the accessToken I obtained earlier is still valid and hasn't been revoked. So, I use acquireTokenSilentWithScopes to check. However, I get an immediate error back saying:
Error raised: 10. Additional Information: Domain: ADAuthenticationErrorDomain Details: The user credentials are need to obtain access token. Please call the non-silent acquireTokenWithResource methods.
What's the right usage of this API such that the token gets silently refreshed or throws an error only when it has been revoked on the server side?
I've managed to beat acquireTokenSilentWithScopes into submission by making the following changes to ADALiOS v3.0.0-pre.2.
Change #1:
ADUserIdentifier has the following class method:
+(BOOL) identifier:(ADUserIdentifier*)identifier matchesInfo:(ADProfileInfo*)info
In it, there are the following lines of code:
NSString* matchString = [identifier userIdMatchString:info];
if (!matchString || [matchString isEqualToString:identifier.userId])
{
return YES;
}
For one reason or another, matchString can sometimes come back as NSNull and calling isEqualToString: method on it will throw. I changed it thusly:
id matchString = [identifier userIdMatchString:info];
if (!matchString || ![matchString isKindOfClass:[NSString class]] || [matchString isEqualToString:identifier.userId])
{
return YES;
}
This seems like a legit bug in the framework that's worth fixing.
Change #2:
When a token is received from AD, ADALiOS tries to store that value in the cache. At some point, it calls ADTokenCacheStoreItem's userCacheKey property, which is defined as follows:
-(NSString*)userCacheKey
{
switch (_identifierType)
{
case OptionalDisplayableId:
case RequiredDisplayableId:
return _profileInfo.username;
case UniqueId:
return _profileInfo.subject;
}
}
In my case, I use RequiredDisplayableId to identify users. In the switch statement above, that translates to _profileInfo.username, which, in turn, returns the preferred_username value from the user profile dictionary. For me that value is not set. So, userCacheKey returns NSNull and the caching mechanism fails.
The values that are set in the user profile dictionary are name and tid. This could be a server misconfiguration, but I worked around the issue by changing the return value of this method to _profileInfo.friendlyName (which maps to name in the user profile dictionary).
Change #3:
The ADKeychainTokenCacheStore, which I use as the concrete ADTokenCacheStoring cache of choice, exposes a sharedGroup property that allows multiple applications to share common keychain secrets. By default, sharedGroup is set to com.microsoft.adalcache. However, since the class is currently private, there is no way to override this value. Also, having that value set requires the iOS app to declare the shared group name in its entitlements. Without these entitlements properly configured, setting values into the keychain fails. So, to work around this issue, I manually set the default sharedGroup value to nil in the ADKeychainTokenCacheStore class itself. I suspect eventually this class will be exposed by the framework as public, but currently that's not the case so I had to hack into it.
Change #4
When I request an auth token from the AD server via the ADALiOS framework, I do so using a policy and a set of scopes. The framework code uses this policy/scope pair to create a lookup key and see if any tokens for that key have already been cached. If none are found, the code contacts the server as expected. Once the server returns an auth token, the framework attempts to cache the value. It constructs a brand new policy/scope key object. However, this time, it uses the policy and scope values that are returned by the server, not the ones I passed in. And, for some reason, the server returns those values to nil. As a result, the new policy/scope key that gets constructed for storage is valid but different from the one I used to look up the cached token initially. So, while the caching operation succeeds, next time I try to look up the auth token using my valid policy/scope pair, the lookup fails.
This may, once again, be a server misconfiguration issue.
Regardless, to fix the problem, I now reset the policy and scope values in the response from the server to the original values I used to generate the server request in the first place. This happens in the following method in ADAuthenticationContext(TokenCaching):
- (void)updateCacheToResult:(ADAuthenticationResult*)result
cacheInstance:(id<ADTokenCacheStoring>)tokenCacheStoreInstance
cacheItem:(ADTokenCacheStoreItem*)cacheItem
withRefreshToken:(NSString*)refreshToken
After all these changes, getting the AD auth token and refreshing it silently seems to work as expected. I'm a little worried about how much I needed to hack into the codebase to make it work. It would be helpful if some MS folks could direct me as to whether these changes were warranted or whether there is a more straight-forward solution.
UPDATE:
It turns out that you don't need to hack into ADKeychainTokenCacheStore directly (change #3 above). The ADAutheticationSettings class exposes a method for you to do so thusly:
[[ADAuthenticationSettings sharedInstance] setSharedCacheKeychainGroup:nil];
I'm Brandon Werner from the Azure Active Directory team. I answered this question here: https://stackoverflow.com/a/44170226/1232116 for the specific question asked.
I was thinking of using MKNetworkKit as base for Network operations for an iOS App. I'm a bit confused about using the initWithHostName:customerHeaders: and initWithHostName: apiPath:customerHeaders: methods.
In the App, I need to communicate to a number of hosts and they have different ways of specifying the Host Name and URL. For example:
HostNameX.com - The Domain Name for HostX,
The first host has the service prepended to the Hostname as so:
serviceA.HostNameX.com?someparam=value The Path for ServiceA (returns info)
serviceB.HostNameX.com?someparam=value The Path for ServiceB (returns info)
HostNameY.com - The Domain Name for HostY
The second host has the service or command appended to the Hostname as so:
HostNameY.com/serviceA?someparam =value The Path for ServiceA (returns info)
HostNameY.com/serviceB?someparam =value The Path for ServiceB (returns info)
For the HostNameY.com case, I was thinking of creating one MKEngine Instance and then passing in the "serviceA?someparam=value" or "serviceB?someparam=value" string as the Path parameter to the operationWithPath:params:httpMethod:ssl: method.
This would work well for what I want to do, however for HostNameX.com I'm not sure how to prepend "serviceA." or "serviceB." to the host name? The only way I can see of doing it is to create two separate MKEngine Instances one for "serviceA" and one for "serviceB". Is this the case?
Am I missing something or is there a way to be able to prepend the service to the domain name after calling initWithHostName:apiPath:customerHeaders: ?
Thanks in advance for any suggestions.
All the Best
Dave
For HostNameY.com, you are right. The service name can be part of the path.
And as you mentioned, you should use multiple instances of MKEngine to manage HostNameX.com.
Why you should use multiple instances :
Hostname is a private property of MKNetworkEngine and the only way to change the hostname is to create a new instance of MKNetworkEngine (without modifying MKNetworkEngine). Hostname property is used by MKNetworkEngine to observe Reachability changes and freeze/restore operations. If you change the hostname at runtime for a single instance of MKNetworkEngine (by making hostname property public for example), this feature won't work well on all situations. Just take a look at freezeOperations (called when you loose network) method in MKNetworkEngine.m. It checks if and operation url contains the current hostname. If you have changed the hostname and the operation was created for another host, the operation will not be archived to restore it later when the network is back.
I solved this problem by using the operationWithURLString and building my own URL String. To me anyway, this seems a much better solution than allocating a number of MKEngine Instances just to handle something that can be passed as a parameter. This way I can also append path components as well as parameters to the URL String, for example:
http://serviceA.hostname.com/fred/simmons?isvip=true&hasBooked=false
Interestingly, the parameter dictionary that is passed to operationWithURLString:params:httpMethod: works in two ways:
if the httpMethod is GET, then the dictionary is used to pass parameters in the URL, if the httpMethod is POST or PUT (or DELETE?) then the Dictionary is formatted and sent as the Request Body.
Question - is it valid to have a POST Command with parameters?
For example:
http://bookingservice.hostname.com/bookings?isvip=true&lastname=simmons
Is this a valid URL as a POST not a GET?
In my app, I have an external monitor that pings the app ever few minutes and measures its uptime / response time Every time the monitor connects, a new server session is created, so when I look at the number of sessions, it's always a minimum of 15, even during times where there are no actual users.
I tried to address this with putting the session creation code into a filter, but that doesn't seem to do it - I guess session automatically gets created when the user opens the first page?
all() {
before = {
if (actionName=='signin') {
def session = request.session //creates session if not exists
}
}
}
I can configure the monitor to pass in a paramter if I need to (i.e. http://servername.com/?nosession, but not sure how to make sure the session isn't created.
Right now there is nothing you can do to prevent the session creation. See: http://jira.codehaus.org/browse/GRAILS-1238
Fortunately, until you are hitting high numbers of requests per second, this isn't a huge problem. One thing we did to get around the false data in our "currently active users" report, was to log the sessions to the database. We create a session record only when the user logs in. Then on specifically mapped URLs, we will "touch" that session record to update the last accessed time. The session record keeps track of user agent, IP, etc and is useful for many reasons. Doing something like this would get around the bogus session count.