springboot2 actuator endpoint not supporting Server Sent Event(SSE) - spring-boot-actuator

I am trying to expose few metrics as a stream using SSE. I am able to consume the SSE event from the restController but when I added custom actuator endpoint , it just closes the connection right way.
#Component
#Endpoint(id = "test")
public class StreamMetrics {
#ReadOperation
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(sequence -> ServerSentEvent.<String> builder()
.id(String.valueOf(sequence))
.event("pingpong")
.data("ping")
.build());
}
}
Result
curl -n -v http://localhost:9080/actuator/test
* Trying ::1:9080...
* TCP_NODELAY set
* Connected to localhost (::1) port 9080 (#0)
> GET /actuator/test HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream;charset=UTF-8
<
id:0
event:pingpong
data:ping
* Connection #0 to host localhost left intact
this is terminated right after the fist event
where as
#RestController
#RequestMapping(value = "/test")
public class SSETest {
#GetMapping("/stream-sse")
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(sequence -> ServerSentEvent.<String> builder()
.id(String.valueOf(sequence))
.event("pingpong")
.data("ping")
.build());
}
}
Result
curl -v -n http://localhost:9080/test/stream-sse
* Trying ::1:9080...
* TCP_NODELAY set
* Connected to localhost (::1) port 9080 (#0)
> GET /test/stream-sse HTTP/1.1
> Host: localhost:9080
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream;charset=UTF-8
<
id:0
event:pingpong
data:ping
id:1
event:pingpong
data:ping
id:2
event:pingpong
data:ping
this goes on without getting terminated.
What is special about endpoint annotation that is terminating the event (continuous flow)?
I tested this in '2.2.4 ' and '2.3.0'

I found an answer. #ReadOperation(produces = "text/event-stream") will alter the default content type application/vnd.spring-boot.actuator.v3+json in actuator endpoints. There was not a document so I looked at the code.
#Component
#Endpoint(id = "test")
public class StreamMetrics {
#ReadOperation(produces = "text/event-stream")
public Flux<ServerSentEvent<String>> streamEvents() {
return Flux.interval(Duration.ofSeconds(1))
.map(sequence -> ServerSentEvent.<String> builder()
.id(String.valueOf(sequence))
.event("pingpong")
.data("ping")
.build());
}
}

Related

While hitting from Postman i am able to access data without authrization but why not with angular 2 application

I am using a ASP.Net Web API. When i am selecting no authentication while creating web API project the same code works but when i use individual authentication it throws 401 error even after trying all possible tricks. Please help me to handle different authentication modes available in ASP. Net project creation. I did not find any proper document on individual authentication even on MSDN.
Also suggest me the recommended way to send token or credentials to the web API.
The below written code is of Angular 2 service. getOneItemDetailsCallClient is working but getOneItemDetailsCall throws 401
getOneItemDetailsCall():Observable<any>{
return this.http.get('http://localhost:56265/api/ProductDetail').map((response:Response)=>response.json());
}
getOneItemDetailsCallClient():Observable<any[]>{
return this.http.get('https://my-json-server.typicode.com/typicode/demo/posts').map((response:Response)=><OneItemComponent[]>response.json());
}
Error details:
Request URL: http://localhost:56265/api/ProductDetail
Request Method: GET
Status Code: 401 Unauthorized
Remote Address: [::1]:56265
Referrer Policy: no-referrer-when-downgrade
Access-Control-Allow-Headers: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Methods: true
Access-Control-Allow-Origin: *
Cache-Control: private
Content-Length: 6161
Content-Type: text/html; charset=utf-8
Date: Thu, 06 Sep 2018 18:04:04 GMT
Server: Microsoft-IIS/10.0
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
X-Powered-By: ASP.NET
X-SourceFiles: =?UTF-8?B?YzpcdXNlcnNcdmlwdWxzaW5naFxkb2N1bWVudHNcdmlzdWFsIHN0dWRpbyAyMDE1XFByb2plY3RzXEZsaXBab25fQmFja2VuZFxGbGlwWm9uX0JhY2tlbmRcYXBpXFByb2R1Y3REZXRhaWw=?=
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-GB,en-US;q=0.9,en;q=0.8
Cache-Control: no-cache
Connection: keep-alive
Host: localhost:56265
Origin: http://localhost:4200
Pragma: no-cache
Referer: http://localhost:4200/OneItem
You are accessing the api from a different domain than the API is hosted so it is blocking your requests. Adding cross origin requests (CORS) should resolve your problem. Here is a link to ASP.NET Core CORS docs.
In Startup.cs You should be able to call:
public void ConfigureServices(IServiceCollection services)
{
services.AddCors();
}
But may need to specify your Angular host app as the origin:
public void Configure(IApplicationBuilder app, IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
// Shows UseCors with CorsPolicyBuilder.
app.UseCors(builder =>
builder.WithOrigins("http://localhost:4200"));
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}

XCode 8, cannot receive push notification

After upgrade to XCode 8, my iphone with IOS 10 cannot receive push notification any more.
I have enable the "Automatically manage signing".
I re-generate the certificate from Apple website, the universal one.
My local codes is running without any error.
I can get device token correctly with the codes (swift 3):
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
print("start didFinishLaunchingWithOptions")
// Register the supported interaction types.
let types = UIUserNotificationType([.alert, .badge, .sound])
let mySettings = UIUserNotificationSettings(types: types, categories: nil)
UIApplication.shared.registerUserNotificationSettings(mySettings)
// Register for remote notifications.
UIApplication.shared.registerForRemoteNotifications()
return true
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("start didRegisterForRemoteNotificationsWithDeviceToken")
let deviceTokenString = ((deviceToken as NSData).description.trimmingCharacters(in: CharacterSet(charactersIn: "<>")) as NSString).replacingOccurrences(of: " ", with: "")
....
}
My PHP in server side always return 200 which is successful.
$ch = curl_init();
if (!defined('CURL_HTTP_VERSION_2_0')) {
define('CURL_HTTP_VERSION_2_0', 3);
}
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0);
$pem_file = '/var/my.pem';
$pem_secret = 'xxxxxx';
$apns_topic = 'xxxxxx';
$apns_expiration = 2;
$change_alert = "{
\"aps\": {
\"alert\": \"$alert_message\",
\"badge\": $badge,
\"sound\": \"default\"
},
\"xxxxxxxx\": \"$xxxxxx\"
}";
$url = "https://api.development.push.apple.com/3/device/$device_token";
//$url = "https://api.push.apple.com/3/device/$device_token";
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POSTFIELDS, $change_alert);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("apns-topic: $apns_topic", "apns-expiration: $apns_expiration"));
curl_setopt($ch, CURLOPT_SSLCERT, $pem_file);
//curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $pem_secret);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
My apns-topic is the same with my app bundle id.
Any idea for this issue?
BTW, XCode 8 is not as stable as the previous version.
===============
CURL logs:
* Trying 17.188.135.156...
* Connected to api.push.apple.com (17.188.135.156) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:#STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/pki/tls/certs/ca-bundle.crt
CApath: none
* SSL connection using TLSv1.2 / ECxxxxxxxxxxxxxxxxxx
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=api.push.apple.com; OU=management:idms.group.533599; O=Apple Inc.; ST=California; C=US
* start date: Aug 28 19:03:46 2015 GMT
* expire date: Sep 26 19:03:46 2017 GMT
* subjectAltName: host "api.push.apple.com" matched cert's "api.push.apple.com"
* issuer: CN=Apple IST CA 2 - G1; OU=Certification Authority; O=Apple Inc.; C=US
* SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* TCP_NODELAY set
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7f2d6eda3970)
> POST /3/device/3xxxxxxxxxxxxxxxxxx HTTP/1.1
Host: api.push.apple.com
Accept: */*
apns-topic: xxxxxxxxxx
apns-expiration: 0
Content-Length: 316
Content-Type: application/x-www-form-urlencoded
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
* We are completely uploaded and fine
< HTTP/2.0 200
< apns-id:AF034xxxxxxxxxxxx0F9F27
<
* Connection #0 to host api.push.apple.com left intact

HTTP GET request with URLConnection is unable to access any page

I am working on Ubuntu 12.04. This is my simple code for implementing HTTP GET method using URLConnection.
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class HttpURLConnectionExample {
private final String USER_AGENT = "Mozilla/5.0";
public static void main(String[] args) throws Exception {
HttpURLConnectionExample http = new HttpURLConnectionExample();
System.out.println("Testing 1 - Send Http GET request");
http.sendGet();
}
// HTTP GET request
private void sendGet() throws Exception {
String url = "https://www.google.com/search?q=flower";
URL obj = new URL(url);
HttpURLConnection con = (HttpURLConnection) obj.openConnection();
// optional default is GET
con.setRequestMethod("GET");
//add request header
con.setRequestProperty("User-Agent", USER_AGENT);
int responseCode = con.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + url);
System.out.println("Response Code : " + responseCode);
BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
//print result
System.out.println(response.toString());
}
}
But when i compile and run this code from ubuntu terminal, the output of this code does not give the content of the page specified by the URL. Rather it gives the following output
Testing 1 - Send Http GET request
Sending 'GET' request to URL : http://www.google.com/search?q=flower
Response Code : 307
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>307 Temporary Redirect</title></head><body><h1>Temporary Redirect</h1><p>The document has moved here.</p><hr><address>Apache/2.2.22 (Fedora) Server at www.google.com Port 80</address></body></html>
This issue holds for any URL I specify in the code. Moreover, I tried to access web content using telnet client like
telnet www.google.com 80
GET /
and it gives the similar result not only for www.google.com but for every URL.
I am a student at IIT Bombay and may be it has something to do with https://ifwb.iitb.ac.in.
I also want to stick to java.net and not apache httpclient. So help me out of this.
It seems you're rejected by the server due incomplete request. It's a good idea to use any sniffer like Fiddler or Wireshark to "learn by example": compare your requests and requests from particular software like browsers.
Below is an excerpt from Wireshark dump, how IE10 sends GET request to interested URL. As you can see, there are various fields that describe capabilities and expectations of your client side so queried server can return the answer in most suitable and consumable form. Consult with Google/RFC to see the meaning of each parameter passed in:
GET /search?q=flower HTTP/1.1
Accept: text/html, application/xhtml+xml, /
Accept-Language: en-US
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64;
Trident/6.0)
Accept-Encoding: gzip, deflate
Host: www.google.com
DNT: 1
Connection: Keep-Alive
Cookie: [some private info here]

Streaming a Response in Symfony2

I am trying this example from the doc: Streaming a Response in Symfony2.
/**
* #param Request $request
* #return Response $render
* #Route("/streamedResponse", name="streamed_response")
* #Template("AcmeTestBundle::streamedResponse.html.twig")
*/
public function streamedResponseAction(Request $request)
{
$response = new StreamedResponse();
$response->setCallback(function () {
echo 'Hello World';
flush();
sleep(3);
echo 'Hello World';
flush();
});
return $response;
}
This outputs everything at the same time. Have I done something wrong?
I tried adding ob_flush() and it seems to be working. Here is my code:
public function streamedAction()
{
$response = new StreamedResponse();
$response->setCallback(function () {
echo 'Hello World';
ob_flush();
flush();
sleep(3);
echo 'Hello World';
ob_flush();
flush();
});
return $response;
}
This returns chunked transfer encoding header with chunked data. Here is output of results:
$ telnet localhost 80
Trying ::1...
Connected to localhost.
Escape character is '^]'.
GET /app_dev.php/streamed HTTP/1.1
Host: symfony21.localdomain
HTTP/1.1 200 OK
Date: Wed, 12 Sep 2012 05:34:12 GMT
Server: Apache/2.2.17 (Unix) DAV/2 mod_ssl/2.2.17 OpenSSL/0.9.8o
cache-control: no-cache, private
x-debug-token: 50501eda7d437
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
b
Hello World
b
Hello World
0
Connection closed by foreign host.
If you see this response in browser, it will display "HelloWorldHelloWorld" after about 3 seconds loading as browser will wait until all chunked data is received as Content-Type is text/*, but when you see the network stream, it is actually doing streaming by sending chunked data.

HTTP 400 error when server fires a long http request to another server

I have a scenario where I hit a cacheRefresh.do URL from an HTTP Client.
It reaches App Server A, A refreshes its own cache and then sends a request (I am using URLConnection) to App Server B,to refresh its cache. (I know its a bad design, but we are out of option)
Now when my request is refresh small cache(small response time) , everything looks fine, I get a 200.
But when my request is to refresh large cache, I get a 400.
Server A and B do get refreshed in this case as well, but why do I get a 400 back as response? Any idea ?
Below is the controller code:
#SuppressWarnings("unused")
public ModelAndView handleRequest(final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
final long cacheRefreshStartTime = System.currentTimeMillis();
final String action = request.getParameter("action");
// Init to 74 since this is the static length that will be appended.
final StringBuffer result = new StringBuffer(74);
final String[] cacheKeys = request.getParameterValues("cacheKeys");
String[] cacheElement = request.getParameterValues("cacheElement");
final String refreshByKeyRegion = request.getParameter("refreshByKeyRegion");
final String refreshByKeyRegionKeys = request.getParameter("refreshByKeyRegionKeys");
final String refreshPartnerInstanceCache = request.getParameter("refreshPartnerInstanceCache");
LOG.debug(" cacheKeys for refresh " + Arrays.toString(cacheKeys));
try {
if (action.equalsIgnoreCase("ALL")) {
performancLogger.info("Cache Refresh requested action=" + action);
this.refreshAllCache();
} else if (action.equalsIgnoreCase("SPECIFIC")) {
performancLogger.info("Cache Refresh requested action=" + action + " keys="
+ Arrays.toString(cacheKeys));
this.refreshSpecificCache(cacheKeys);
} else if (action.equalsIgnoreCase("cacheElement")) {
if (refreshByKeyRegion != null && refreshByKeyRegion.length() > 0 && refreshByKeyRegionKeys != null
&& refreshByKeyRegionKeys.length() > 0) {
cacheElement = new String[] { refreshByKeyRegion + "," + refreshByKeyRegionKeys };
}
performancLogger.info("Cache Refresh requested action=" + action + " element="
+ Arrays.toString(cacheElement));
this.refreshCacheElements(cacheElement);
}
if (!request.getServerName().contains("localhost") && refreshPartnerInstanceCache != null
&& refreshPartnerInstanceCache.equals("true")) {
refreshPartnerInstanceCache(request);
}
result.append("Cache refresh completed successfully.");
if (cacheKeys != null) {
result.append(" Keys - ");
result.append(this.formatArrayAsString(cacheKeys));
}
} catch (final Exception e) {
result.append("Cache refresh failed.");
if (cacheKeys != null) {
result.append(" Keys - ");
result.append(this.formatArrayAsString(cacheKeys));
}
}
if (action != null) {
performancLogger.info("Cache Refresh competed total refresh time = "
+ formatElapsedTime(System.currentTimeMillis() - cacheRefreshStartTime));
}
return new ModelAndView(IVRControllerNames.CACHE_REFRESH_STANDARD_VIEW, "displayInfo", this
.getDisplayInfo(result));
}
Request Header:
POST xxxxx.do HTTP/1.1
Host: xxxxx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
Referer: http://xxx/yyy/zzz/cacheView.do
Cookie: JSESSIONID=xxxxx.x.x
Request Body:
Content-Type: application/x-www-form-urlencoded
Content-Length: 134
action=SPECIFIC&refreshPartnerInstanceCache=true&cacheKeys=xxxx&cacheKeys=xxx&Refresh=yyyy
Thanks!
In your action you seem to have several keys with the same name.
action=SPECIFIC&refreshPartnerInstanceCache=true&**cacheKeys**=xxxx&**cacheKeys**=xxx&Refresh=yyyy
Clean it up and give them unique IDs.
Also, they do not seem to be urlencoded which may cause trouble if your keys contain funny characters. See How to escape URL-encoded data in POST with HttpWebRequest

Resources