google-api-java-client-services
google-api-java-client-services copied to clipboard
Question(Calendar API) : Getting to many push notifications, Is there a way to receive them only once?
Hi I am using the watch to receive push notifications when events change.
I have successfully created a channel.
{
"expiration": 1714380331000,
"id": "<id>",
"kind": "api#channel",
"resourceId": "<resourceId>",
"resourceUri": "https://www.googleapis.com/calendar/v3/calendars/primary/events?alt=json",
"token": "tokenValue"
}
..and I received a push notification with the following code:
@PostMapping("/notifications")
public ResponseEntity<List<GoogleCalendarEventResponse>> printNotification(@RequestHeader(WebhookHeaders.RESOURCE_ID) String resourceId,
@RequestHeader(WebhookHeaders.RESOURCE_URI) String resourceUri,
@RequestHeader(WebhookHeaders.CHANNEL_ID) String channelId,
@RequestHeader(WebhookHeaders.CHANNEL_EXPIRATION) String channelExpiration,
@RequestHeader(WebhookHeaders.RESOURCE_STATE) String resourceState,
@RequestHeader(WebhookHeaders.MESSAGE_NUMBER) String messageNumber) {
googleService.listEvents(channelId);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
I've successfully managed to receive push notifications and store changed events in the database, but I'm encountering an issue of receiving too many push notifications.
Am I doing something wrong? Or if this issue is a bug, I'd like to contribute.
using library : com.google.apis:google-api-services-calendar:v3-rev20231123-2.0.0
Hi @YongGoose, thanks for reporting this. From what I understand you have an application that launches a request to Calendar.Events.Watch
. However the log shows many POST requests to this endpoint occurring, which makes me suspect that we are getting a similar set of notifications after each POST.
Would you mind sharing a bit on your use case, hoping to understand why this POST occurs many times? I'm just trying to narrow down this (maybe it's the suspected repeated set of notifications what worries us).
Anyhow, a small reproducible example (or snippet) would be of great help troubleshoot this. Would you mind sharing one if your time allows?
@diegomarquezp Of course!! Thank you for kindly answering my question.
The process of creating a channel
.
After signing in through the servlet, it redirects to the specified callback URL. /oauth2Callback
@WebServlet(urlPatterns = "/oauth2Login")
public class GoogleAuthenticationServlet extends AbstractAuthorizationCodeServlet {
@Override
protected AuthorizationCodeFlow initializeFlow() {
return GoogleUtils.initializeFlow();
}
@Override
protected String getRedirectUri(HttpServletRequest httpServletRequest) {
return GoogleUtils.getRedirectUri(httpServletRequest);
}
@Override
protected String getUserId(HttpServletRequest httpServletRequest) {
return GoogleUtils.getClientId(httpServletRequest);
}
}
It calls the /google/watch
URL with the accessToken and refreshToken, which are necessary pieces of information for issuing a channel, stored in cookies.
@WebServlet(urlPatterns = "/oauth2callback")
public class Oauth2CallbackServlet extends AbstractAuthorizationCodeCallbackServlet {
@Override
protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
throws IOException {
String userId = req.getSession().getId();
ResponseCookie accessTokenCookie = ResponseCookie.from(ACCESS_TOKEN, credential.getAccessToken())
.httpOnly(true)
.build();
ResponseCookie refreshTokenCookie = ResponseCookie.from(REFRESH_TOKEN, credential.getRefreshToken())
.httpOnly(true)
.build();
resp.addHeader(HttpHeaders.SET_COOKIE, accessTokenCookie.toString());
resp.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
resp.sendRedirect("/google/watch?userId=" + userId);
}
...
After receiving the necessary information from the callback servlet, it creates a channel.
//@RestController + @RequiredMapping
@RestControllerWithMapping("/google")
@RequiredArgsConstructor
public class GoogleCalendarControllerImpl implements GoogleCalendarController {
private final GoogleService googleService;
...
@GetMapping("/watch")
public ResponseEntity<Channel> watchCalendar(@RequestParam String userId,
HttpServletRequest request) {
TokenResponse tokenResponse = createTokenResponse(request.getCookies());
String accessToken = AUTHORIZATION_BEARER + tokenResponse.getAccessToken();
HttpHeaders headers = new HttpHeaders();
headers.add(AUTHORIZATION, accessToken);
Channel channel = googleService.executeWatchRequest(userId);
googleService.registGoogleCredentials(channel, tokenResponse, userId);
return ResponseEntity.ok()
.headers(headers)
.body(channel);
}
...
create a channel
with credential & watch
/*
The channel object used here is initialized through the @PostConstruct annotation,
with the address set to "/google/notifications".
CALENDAR_ID -> primary
*/
public Channel executeWatchRequest(String userId) {
try {
Calendar.Events.Watch watch = calendarService(userId)
.events()
.watch(CALENDAR_ID, channel);
return watch.execute();
} catch (IOException e) {
throw new GoogleCalendarWatchException();
}
}
public Calendar calendarService(String userId) {
Credential credential = getCredentials(userId);
return new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
public static Credential getCredentials(String userId) {
try {
return flow.loadCredential(userId);
} catch (IOException e) {
throw new GoogleCredentialException();
}
}
After changes occur in the events, it receives push notifications at "google/notifications"
, which is the channel's address.
//@RestController + @RequiredMapping
@RestControllerWithMapping("/google")
@RequiredArgsConstructor
public class GoogleCalendarControllerImpl implements GoogleCalendarController {
private final GoogleService googleService;
@PostMapping("/notifications")
public ResponseEntity<List<GoogleCalendarEventResponse>> printNotification(@RequestHeader(WebhookHeaders.RESOURCE_ID) String resourceId,
@RequestHeader(WebhookHeaders.RESOURCE_URI) String resourceUri,
@RequestHeader(WebhookHeaders.CHANNEL_ID) String channelId,
@RequestHeader(WebhookHeaders.CHANNEL_EXPIRATION) String channelExpiration,
@RequestHeader(WebhookHeaders.RESOURCE_STATE) String resourceState,
@RequestHeader(WebhookHeaders.MESSAGE_NUMBER) String messageNumber) {
googleService.listEvents(channelId);
return ResponseEntity.status(HttpStatus.CREATED).build();
}
In the following scenario, multiple push notifications can be received:
create an event
This is the received push notification after the event is generated. I checked the logs via AWS CloudWatch. http://colorscripter.com/s/w9djP1f I uploaded the log to Color Scripter due to its length and shared the link.
Upon checking the database, I confirmed that the data was successfully stored.
If you need more information, feel free to let me know in the comments! wish you have a gread day🙂
Closing this issue as we are unable to provide adequate API-specific customer support through this forum. https://cloud.google.com/support is best able to provide timely answers to API-specific questions.