SlideShare a Scribd company logo
Creating an Uber Clone - Part XII
Continuing the server side code we’ll now delve into the location logic which maps to the web socket support in Spring Boot
WebSockets
✦Special socket type created through HTTP or HTTPS
request
✦They are more compatible and pass through firewalls
like an HTTP connection
✦We will use binary sockets here
✦Why not use WebSockets for everything?
© Codename One 2017 all rights reserved
WebSocket is a special type of socket that is created through HTTP or HTTPS request. A webserver that supports websockets opens a regular HTTP connection and
then uses the socket opened there to continue working as a regular socket. As a result the websocket setup is slower than a regular TCP socket but they provide the
same level of flexibility after creation. 

The advantage over TCP sockets is compatibility and the ability to pass through potentially problematic firewalls as those would see a WebSocket as another HTTP
connection.

The WebSocket API includes two types of packets: Text and Binary. In this case I'll use the binary protocol because it's pretty easy to do this in Java.

Up until now all our communications went through webservices which is convenient and scalable. The fact we can use tools like curl and the network monitor to see what
is going on under the hood is very powerful. However, webservices suffer from the performance overhead and fixed structure issues of HTTP. For more interactive data
we would prefer something like websockets.

Some people use websockets for all their communications and it might work for your use cases. A lot of developers use the text based websocket as a substitute to
webservices altogether and in some cases that makes sense. However, as I mentioned before we have decades of experience with HTTP. It works well and has a huge
infrastructure of tools behind it.

Websockets are a low level API. There are some higher level abstractions on top of them but these often go back to the problems of HTTP without giving much in return.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container =
new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/wsMsg");
}
@Bean
public WebSocketHandler myHandler() {
return new Handler();
}
}
WebSocketConfig
Spring Boot has decent support for WebSockets but you need to activate it first. We need to define a configuration class that sets up the WebSocket environment. This
class serves as a configuration tool for the websocket API defining limits, quotas and handlers.

Here I set common configuration arguments for websocket messages setting buffer sizes for the different types
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container =
new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}
@Override
public void registerWebSocketHandlers(
WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/wsMsg");
}
@Bean
public WebSocketHandler myHandler() {
return new Handler();
}
}
WebSocketConfig
Here I bind the Handler class to the wsMsg URL which will receive all of the websocket callbacks. Before we go into the handler class lets create a special service class
to handle location based callbacks similarly to the User Service
@Service
public class LocationService {
@Autowired
private UserRepository users;
public void updateUserLocation(String token, double lat, double lon, float dir) {
List<User> us = users.findByAuthToken(token);
User u = us.get(0);
u.setLatitude(lat);
u.setLongitude(lat);
u.setDirection(dir);
users.save(u);
}
public List<UserDAO> findAllDrivers(double lat, double lon, double radius) {
double minLat = lat - radius * 0.009044;
double minLon = lon - radius * 0.0089831;
double maxLat = lat + radius * 0.009044;
double maxLon = lon + radius * 0.0089831;
return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon));
}
public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) {
LocationService
Most of the location API's map to the User class but it's logically separate from the UserService
@Service
public class LocationService {
@Autowired
private UserRepository users;
public void updateUserLocation(String token, double lat, double lon, float dir) {
List<User> us = users.findByAuthToken(token);
User u = us.get(0);
u.setLatitude(lat);
u.setLongitude(lat);
u.setDirection(dir);
users.save(u);
}
public List<UserDAO> findAllDrivers(double lat, double lon, double radius) {
double minLat = lat - radius * 0.009044;
double minLon = lon - radius * 0.0089831;
double maxLat = lat + radius * 0.009044;
double maxLon = lon + radius * 0.0089831;
return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon));
}
public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) {
LocationService
We will periodically update the users location, notice that location can only be updated by the user himself as the token is required for that operation
@Service
public class LocationService {
@Autowired
private UserRepository users;
public void updateUserLocation(String token, double lat, double lon, float dir) {
List<User> us = users.findByAuthToken(token);
User u = us.get(0);
u.setLatitude(lat);
u.setLongitude(lat);
u.setDirection(dir);
users.save(u);
}
public List<UserDAO> findAllDrivers(double lat, double lon, double radius) {
double minLat = lat - radius * 0.009044;
double minLon = lon - radius * 0.0089831;
double maxLat = lat + radius * 0.009044;
double maxLon = lon + radius * 0.0089831;
return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon));
}
public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) {
LocationService
It's more intuitive to work with radius from the client but the JPA query language makes it easier to work in absolute coordinates so I convert the kilometer radius unit to
latitude/longitude values
public List<UserDAO> findAllDrivers(double lat, double lon, double radius) {
double minLat = lat - radius * 0.009044;
double minLon = lon - radius * 0.0089831;
double maxLat = lat + radius * 0.009044;
double maxLon = lon + radius * 0.0089831;
return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon));
}
public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) {
double minLat = lat - radius * 0.009044;
double minLon = lon - radius * 0.0089831;
double maxLat = lat + radius * 0.009044;
double maxLon = lon + radius * 0.0089831;
return toDaoList(users.findByAvailableDriver( minLat, maxLat, minLon, maxLon));
}
private List<UserDAO> toDaoList(List<User> us) {
ArrayList<UserDAO> respone = new ArrayList<>();
for(User u : us) {
respone.add(u.getPartialDao());
}
return respone;
}
LocationService
We have two versions of the query, one finds all of the drivers in the area so we can draw them on the map. The second searches for available drivers only for hailing
purposes
public List<UserDAO> findAllDrivers(double lat, double lon, double radius) {
double minLat = lat - radius * 0.009044;
double minLon = lon - radius * 0.0089831;
double maxLat = lat + radius * 0.009044;
double maxLon = lon + radius * 0.0089831;
return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon));
}
public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) {
double minLat = lat - radius * 0.009044;
double minLon = lon - radius * 0.0089831;
double maxLat = lat + radius * 0.009044;
double maxLon = lon + radius * 0.0089831;
return toDaoList(users.findByAvailableDriver( minLat, maxLat, minLon, maxLon));
}
private List<UserDAO> toDaoList(List<User> us) {
ArrayList<UserDAO> respone = new ArrayList<>();
for(User u : us) {
respone.add(u.getPartialDao());
}
return respone;
}
LocationService
I use a version of the method that only returns a part of the user data as we normally don't need all of the data. Once this is in place we can implement the handler class
which is the actual WebSocket implementation.
short messageType;
short tokenLength;
byte[] token;
double latitude;
double longitude;
float direction;
double radius;
byte seeking;
Handler - Packet structure for location update
But first let’s review the communication protocol… This is the binary structure we will use when receiving a request on the server for a location update. So when a user
changes his current location he will send this data…

The message type should be 1 for a location update from the user
short messageType;
short tokenLength;
byte[] token;
double latitude;
double longitude;
float direction;
double radius;
byte seeking;
Handler - Packet structure for location update
The length of the user token string followed by a byte array of the token length representing the string. Notice that I used bytes instead of chars. Since the token is 100%
ASCII I can rely on that fact and reduce the packet size further
short messageType;
short tokenLength;
byte[] token;
double latitude;
double longitude;
float direction;
double radius;
byte seeking;
Handler - Packet structure for location update
The location data and the radius/direction of the user
short messageType;
short tokenLength;
byte[] token;
double latitude;
double longitude;
float direction;
double radius;
byte seeking;
Handler - Packet structure for location update
A byte which is set to 1 when we are hailing a taxi in which case it will seek only the available drivers. 

Once this packet is processed the server would return the cars within the search radius by sending a packet back.
short messageType;
int responseSize;
long driverId;
double latitude;
double longitude;
float direction;
Handler - Packet structure for response
In this case we don't need the token as this is a message from the server. The response type can be 2 for driver position update and 3 for available driver position
update.
short messageType;
int responseSize;
long driverId;
double latitude;
double longitude;
float direction;
Handler - Packet structure for response
This entry indicates the number of drivers in the returned data
short messageType;
int responseSize;
long driverId;
double latitude;
double longitude;
float direction;
Handler - Packet structure for response
The rest of the lines repeat for every driver responseSize times and include the position data for every driver. Now that we understand the protocol lets dig into the code
that implements it
public class Handler extends BinaryWebSocketHandler {
public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1;
public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2;
public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3;
@Autowired
private LocationService loc;
@Override
protected void handleBinaryMessage(WebSocketSession session,
BinaryMessage message) throws Exception {
ByteBuffer b = message.getPayload();
short messageType = b.getShort();
short stringLength = b.getShort();
StringBuilder bld = new StringBuilder();
for(int iter = 0 ; iter < stringLength ; iter++) {
bld.append((char)b.get());
}
String token = bld.toString();
switch(messageType) {
case MESSAGE_TYPE_LOCATION_UPDATE:
Handler
The handler class is a binary web socket handler that receives callbacks on incoming packets. Lets go over the code.

These are constants used in the binary protocol to communicate the type of request or response.
public class Handler extends BinaryWebSocketHandler {
public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1;
public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2;
public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3;
@Autowired
private LocationService loc;
@Override
protected void handleBinaryMessage(WebSocketSession session,
BinaryMessage message) throws Exception {
ByteBuffer b = message.getPayload();
short messageType = b.getShort();
short stringLength = b.getShort();
StringBuilder bld = new StringBuilder();
for(int iter = 0 ; iter < stringLength ; iter++) {
bld.append((char)b.get());
}
String token = bld.toString();
switch(messageType) {
case MESSAGE_TYPE_LOCATION_UPDATE:
Handler
This is a callback for a binary messages from the client
public class Handler extends BinaryWebSocketHandler {
public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1;
public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2;
public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3;
@Autowired
private LocationService loc;
@Override
protected void handleBinaryMessage(WebSocketSession session,
BinaryMessage message) throws Exception {
ByteBuffer b = message.getPayload();
short messageType = b.getShort();
short stringLength = b.getShort();
StringBuilder bld = new StringBuilder();
for(int iter = 0 ; iter < stringLength ; iter++) {
bld.append((char)b.get());
}
String token = bld.toString();
switch(messageType) {
case MESSAGE_TYPE_LOCATION_UPDATE:
Handler
The API works with NIO's byte buffer which allows us to run through a request efficiently
public class Handler extends BinaryWebSocketHandler {
public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1;
public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2;
public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3;
@Autowired
private LocationService loc;
@Override
protected void handleBinaryMessage(WebSocketSession session,
BinaryMessage message) throws Exception {
ByteBuffer b = message.getPayload();
short messageType = b.getShort();
short stringLength = b.getShort();
StringBuilder bld = new StringBuilder();
for(int iter = 0 ; iter < stringLength ; iter++) {
bld.append((char)b.get());
}
String token = bld.toString();
switch(messageType) {
case MESSAGE_TYPE_LOCATION_UPDATE:
Handler
We get the length of the user token string and the byte array. Again I used bytes instead of chars. Since the token is 100% ASCII we can rely on that
String token = bld.toString();
switch(messageType) {
case MESSAGE_TYPE_LOCATION_UPDATE:
double lat = b.getDouble();
double lon = b.getDouble();
float dir = b.getFloat();
double radius = b.getDouble();
boolean seeking = b.get() == 1;
loc.updateUserLocation(token, lat, lon, dir);
List<UserDAO> response;
short responseType;
if(seeking) {
response = loc.findAvailableDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_DRIVER_POSITIONS;
} else {
response = loc.findAllDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS;
}
if(response != null && response.size() > 0) {
try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);) {
dos.writeShort(responseType);
dos.writeInt(response.size());
Handler
Assuming this is a location update we pull out the data and update the User object
String token = bld.toString();
switch(messageType) {
case MESSAGE_TYPE_LOCATION_UPDATE:
double lat = b.getDouble();
double lon = b.getDouble();
float dir = b.getFloat();
double radius = b.getDouble();
boolean seeking = b.get() == 1;
loc.updateUserLocation(token, lat, lon, dir);
List<UserDAO> response;
short responseType;
if(seeking) {
response = loc.findAvailableDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_DRIVER_POSITIONS;
} else {
response = loc.findAllDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS;
}
if(response != null && response.size() > 0) {
try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);) {
dos.writeShort(responseType);
dos.writeInt(response.size());
Handler
We prepare to return a response based on the seeking flag, we also need to mark the response type correctly
if(seeking) {
response = loc.findAvailableDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_DRIVER_POSITIONS;
} else {
response = loc.findAllDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS;
}
if(response != null && response.size() > 0) {
try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);) {
dos.writeShort(responseType);
dos.writeInt(response.size());
for(UserDAO u : response) {
dos.writeLong(u.getId());
dos.writeDouble(u.getLatitude());
dos.writeDouble(u.getLongitude());
dos.writeFloat(u.getDirection());
}
dos.flush();
BinaryMessage bin = new BinaryMessage(bos.toByteArray());
session.sendMessage(bin);
}
}
Handler
I used a byte array output stream to construct the response, I use try with resources to close the streams automatically when I'm done. I just write out the response data
to the steam
if(seeking) {
response = loc.findAvailableDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_DRIVER_POSITIONS;
} else {
response = loc.findAllDrivers(lat, lon, radius);
responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS;
}
if(response != null && response.size() > 0) {
try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);) {
dos.writeShort(responseType);
dos.writeInt(response.size());
for(UserDAO u : response) {
dos.writeLong(u.getId());
dos.writeDouble(u.getLatitude());
dos.writeDouble(u.getLongitude());
dos.writeFloat(u.getDirection());
}
dos.flush();
BinaryMessage bin = new BinaryMessage(bos.toByteArray());
session.sendMessage(bin);
}
}
Handler
And finally we convert the byte array data from the stream to a byte array then send to the client. This is it for the basic server code…

More Related Content

PDF
Creating an Uber Clone - Part XII.pdf
PDF
Creating an Uber Clone - Part XV - Transcript.pdf
PDF
Creating an Uber Clone - Part XV.pdf
PDF
Creating an Uber Clone - Part XI - Transcript.pdf
PDF
Creating an Uber Clone - Part XXVI - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part XV - Transcript.pdf
PDF
Ajug - The Spring Update
PDF
Greach 2019 - Creating Micronaut Configurations
Creating an Uber Clone - Part XII.pdf
Creating an Uber Clone - Part XV - Transcript.pdf
Creating an Uber Clone - Part XV.pdf
Creating an Uber Clone - Part XI - Transcript.pdf
Creating an Uber Clone - Part XXVI - Transcript.pdf
Creating a Whatsapp Clone - Part XV - Transcript.pdf
Ajug - The Spring Update
Greach 2019 - Creating Micronaut Configurations

Similar to Creating an Uber Clone - Part XII - Transcript.pdf (20)

PDF
The Spring Update
PDF
Creating a Facebook Clone - Part XXV - Transcript.pdf
PDF
Creating an Uber Clone - Part XXVII - Transcript.pdf
PPTX
Endpoint node.js framework presentation
PDF
Introduction to Spring Boot.pdf
PPTX
SpringBootCompleteBootcamp.pptx
PDF
Creating a Whatsapp Clone - Part II - Transcript.pdf
PDF
Spring Web Services: SOAP vs. REST
PDF
Building APIs in an easy way using API Platform
PDF
Microservices for the Masses with Spring Boot, JHipster, and OAuth - Jforum S...
PDF
Rest web service_with_spring_hateoas
PDF
Spring 4 Web App
PDF
Creating an Uber Clone - Part XXIX - Transcript.pdf
PDF
Microservices for the Masses with Spring Boot, JHipster, and OAuth - South We...
PDF
Microservices with Micronaut
PPTX
Das kannste schon so machen
PDF
PDF
GWT Enterprise Edition
PDF
Creating native apps with WordPress
PDF
Building Web Services
The Spring Update
Creating a Facebook Clone - Part XXV - Transcript.pdf
Creating an Uber Clone - Part XXVII - Transcript.pdf
Endpoint node.js framework presentation
Introduction to Spring Boot.pdf
SpringBootCompleteBootcamp.pptx
Creating a Whatsapp Clone - Part II - Transcript.pdf
Spring Web Services: SOAP vs. REST
Building APIs in an easy way using API Platform
Microservices for the Masses with Spring Boot, JHipster, and OAuth - Jforum S...
Rest web service_with_spring_hateoas
Spring 4 Web App
Creating an Uber Clone - Part XXIX - Transcript.pdf
Microservices for the Masses with Spring Boot, JHipster, and OAuth - South We...
Microservices with Micronaut
Das kannste schon so machen
GWT Enterprise Edition
Creating native apps with WordPress
Building Web Services
Ad

More from ShaiAlmog1 (20)

PDF
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
PDF
create-netflix-clone-06-client-ui.pdf
PDF
create-netflix-clone-01-introduction_transcript.pdf
PDF
create-netflix-clone-02-server_transcript.pdf
PDF
create-netflix-clone-04-server-continued_transcript.pdf
PDF
create-netflix-clone-01-introduction.pdf
PDF
create-netflix-clone-06-client-ui_transcript.pdf
PDF
create-netflix-clone-03-server.pdf
PDF
create-netflix-clone-04-server-continued.pdf
PDF
create-netflix-clone-05-client-model_transcript.pdf
PDF
create-netflix-clone-03-server_transcript.pdf
PDF
create-netflix-clone-02-server.pdf
PDF
create-netflix-clone-05-client-model.pdf
PDF
Creating a Whatsapp Clone - Part II.pdf
PDF
Creating a Whatsapp Clone - Part IX - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part V - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IV.pdf
PDF
Creating a Whatsapp Clone - Part I - Transcript.pdf
PDF
Creating a Whatsapp Clone - Part IX.pdf
The Duck Teaches Learn to debug from the masters. Local to production- kill ...
create-netflix-clone-06-client-ui.pdf
create-netflix-clone-01-introduction_transcript.pdf
create-netflix-clone-02-server_transcript.pdf
create-netflix-clone-04-server-continued_transcript.pdf
create-netflix-clone-01-introduction.pdf
create-netflix-clone-06-client-ui_transcript.pdf
create-netflix-clone-03-server.pdf
create-netflix-clone-04-server-continued.pdf
create-netflix-clone-05-client-model_transcript.pdf
create-netflix-clone-03-server_transcript.pdf
create-netflix-clone-02-server.pdf
create-netflix-clone-05-client-model.pdf
Creating a Whatsapp Clone - Part II.pdf
Creating a Whatsapp Clone - Part IX - Transcript.pdf
Creating a Whatsapp Clone - Part V - Transcript.pdf
Creating a Whatsapp Clone - Part IV - Transcript.pdf
Creating a Whatsapp Clone - Part IV.pdf
Creating a Whatsapp Clone - Part I - Transcript.pdf
Creating a Whatsapp Clone - Part IX.pdf
Ad

Recently uploaded (20)

PPTX
Cloud computing and distributed systems.
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Network Security Unit 5.pdf for BCA BBA.
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Spectral efficient network and resource selection model in 5G networks
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
KodekX | Application Modernization Development
PPTX
Spectroscopy.pptx food analysis technology
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Cloud computing and distributed systems.
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Chapter 3 Spatial Domain Image Processing.pdf
Dropbox Q2 2025 Financial Results & Investor Presentation
MYSQL Presentation for SQL database connectivity
Unlocking AI with Model Context Protocol (MCP)
Building Integrated photovoltaic BIPV_UPV.pdf
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
NewMind AI Weekly Chronicles - August'25 Week I
Network Security Unit 5.pdf for BCA BBA.
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Spectral efficient network and resource selection model in 5G networks
The AUB Centre for AI in Media Proposal.docx
Reach Out and Touch Someone: Haptics and Empathic Computing
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Mobile App Security Testing_ A Comprehensive Guide.pdf
Digital-Transformation-Roadmap-for-Companies.pptx
KodekX | Application Modernization Development
Spectroscopy.pptx food analysis technology
Agricultural_Statistics_at_a_Glance_2022_0.pdf

Creating an Uber Clone - Part XII - Transcript.pdf

  • 1. Creating an Uber Clone - Part XII Continuing the server side code we’ll now delve into the location logic which maps to the web socket support in Spring Boot
  • 2. WebSockets ✦Special socket type created through HTTP or HTTPS request ✦They are more compatible and pass through firewalls like an HTTP connection ✦We will use binary sockets here ✦Why not use WebSockets for everything? © Codename One 2017 all rights reserved WebSocket is a special type of socket that is created through HTTP or HTTPS request. A webserver that supports websockets opens a regular HTTP connection and then uses the socket opened there to continue working as a regular socket. As a result the websocket setup is slower than a regular TCP socket but they provide the same level of flexibility after creation. The advantage over TCP sockets is compatibility and the ability to pass through potentially problematic firewalls as those would see a WebSocket as another HTTP connection. The WebSocket API includes two types of packets: Text and Binary. In this case I'll use the binary protocol because it's pretty easy to do this in Java. Up until now all our communications went through webservices which is convenient and scalable. The fact we can use tools like curl and the network monitor to see what is going on under the hood is very powerful. However, webservices suffer from the performance overhead and fixed structure issues of HTTP. For more interactive data we would prefer something like websockets. Some people use websockets for all their communications and it might work for your use cases. A lot of developers use the text based websocket as a substitute to webservices altogether and in some cases that makes sense. However, as I mentioned before we have decades of experience with HTTP. It works well and has a huge infrastructure of tools behind it. Websockets are a low level API. There are some higher level abstractions on top of them but these often go back to the problems of HTTP without giving much in return.
  • 3. @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); return container; } @Override public void registerWebSocketHandlers( WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), "/wsMsg"); } @Bean public WebSocketHandler myHandler() { return new Handler(); } } WebSocketConfig Spring Boot has decent support for WebSockets but you need to activate it first. We need to define a configuration class that sets up the WebSocket environment. This class serves as a configuration tool for the websocket API defining limits, quotas and handlers. Here I set common configuration arguments for websocket messages setting buffer sizes for the different types
  • 4. @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); return container; } @Override public void registerWebSocketHandlers( WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), "/wsMsg"); } @Bean public WebSocketHandler myHandler() { return new Handler(); } } WebSocketConfig Here I bind the Handler class to the wsMsg URL which will receive all of the websocket callbacks. Before we go into the handler class lets create a special service class to handle location based callbacks similarly to the User Service
  • 5. @Service public class LocationService { @Autowired private UserRepository users; public void updateUserLocation(String token, double lat, double lon, float dir) { List<User> us = users.findByAuthToken(token); User u = us.get(0); u.setLatitude(lat); u.setLongitude(lat); u.setDirection(dir); users.save(u); } public List<UserDAO> findAllDrivers(double lat, double lon, double radius) { double minLat = lat - radius * 0.009044; double minLon = lon - radius * 0.0089831; double maxLat = lat + radius * 0.009044; double maxLon = lon + radius * 0.0089831; return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon)); } public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) { LocationService Most of the location API's map to the User class but it's logically separate from the UserService
  • 6. @Service public class LocationService { @Autowired private UserRepository users; public void updateUserLocation(String token, double lat, double lon, float dir) { List<User> us = users.findByAuthToken(token); User u = us.get(0); u.setLatitude(lat); u.setLongitude(lat); u.setDirection(dir); users.save(u); } public List<UserDAO> findAllDrivers(double lat, double lon, double radius) { double minLat = lat - radius * 0.009044; double minLon = lon - radius * 0.0089831; double maxLat = lat + radius * 0.009044; double maxLon = lon + radius * 0.0089831; return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon)); } public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) { LocationService We will periodically update the users location, notice that location can only be updated by the user himself as the token is required for that operation
  • 7. @Service public class LocationService { @Autowired private UserRepository users; public void updateUserLocation(String token, double lat, double lon, float dir) { List<User> us = users.findByAuthToken(token); User u = us.get(0); u.setLatitude(lat); u.setLongitude(lat); u.setDirection(dir); users.save(u); } public List<UserDAO> findAllDrivers(double lat, double lon, double radius) { double minLat = lat - radius * 0.009044; double minLon = lon - radius * 0.0089831; double maxLat = lat + radius * 0.009044; double maxLon = lon + radius * 0.0089831; return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon)); } public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) { LocationService It's more intuitive to work with radius from the client but the JPA query language makes it easier to work in absolute coordinates so I convert the kilometer radius unit to latitude/longitude values
  • 8. public List<UserDAO> findAllDrivers(double lat, double lon, double radius) { double minLat = lat - radius * 0.009044; double minLon = lon - radius * 0.0089831; double maxLat = lat + radius * 0.009044; double maxLon = lon + radius * 0.0089831; return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon)); } public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) { double minLat = lat - radius * 0.009044; double minLon = lon - radius * 0.0089831; double maxLat = lat + radius * 0.009044; double maxLon = lon + radius * 0.0089831; return toDaoList(users.findByAvailableDriver( minLat, maxLat, minLon, maxLon)); } private List<UserDAO> toDaoList(List<User> us) { ArrayList<UserDAO> respone = new ArrayList<>(); for(User u : us) { respone.add(u.getPartialDao()); } return respone; } LocationService We have two versions of the query, one finds all of the drivers in the area so we can draw them on the map. The second searches for available drivers only for hailing purposes
  • 9. public List<UserDAO> findAllDrivers(double lat, double lon, double radius) { double minLat = lat - radius * 0.009044; double minLon = lon - radius * 0.0089831; double maxLat = lat + radius * 0.009044; double maxLon = lon + radius * 0.0089831; return toDaoList(users.findByDriver(minLat, maxLat, minLon, maxLon)); } public List<UserDAO> findAvailableDrivers(double lat, double lon, double radius) { double minLat = lat - radius * 0.009044; double minLon = lon - radius * 0.0089831; double maxLat = lat + radius * 0.009044; double maxLon = lon + radius * 0.0089831; return toDaoList(users.findByAvailableDriver( minLat, maxLat, minLon, maxLon)); } private List<UserDAO> toDaoList(List<User> us) { ArrayList<UserDAO> respone = new ArrayList<>(); for(User u : us) { respone.add(u.getPartialDao()); } return respone; } LocationService I use a version of the method that only returns a part of the user data as we normally don't need all of the data. Once this is in place we can implement the handler class which is the actual WebSocket implementation.
  • 10. short messageType; short tokenLength; byte[] token; double latitude; double longitude; float direction; double radius; byte seeking; Handler - Packet structure for location update But first let’s review the communication protocol… This is the binary structure we will use when receiving a request on the server for a location update. So when a user changes his current location he will send this data… The message type should be 1 for a location update from the user
  • 11. short messageType; short tokenLength; byte[] token; double latitude; double longitude; float direction; double radius; byte seeking; Handler - Packet structure for location update The length of the user token string followed by a byte array of the token length representing the string. Notice that I used bytes instead of chars. Since the token is 100% ASCII I can rely on that fact and reduce the packet size further
  • 12. short messageType; short tokenLength; byte[] token; double latitude; double longitude; float direction; double radius; byte seeking; Handler - Packet structure for location update The location data and the radius/direction of the user
  • 13. short messageType; short tokenLength; byte[] token; double latitude; double longitude; float direction; double radius; byte seeking; Handler - Packet structure for location update A byte which is set to 1 when we are hailing a taxi in which case it will seek only the available drivers. Once this packet is processed the server would return the cars within the search radius by sending a packet back.
  • 14. short messageType; int responseSize; long driverId; double latitude; double longitude; float direction; Handler - Packet structure for response In this case we don't need the token as this is a message from the server. The response type can be 2 for driver position update and 3 for available driver position update.
  • 15. short messageType; int responseSize; long driverId; double latitude; double longitude; float direction; Handler - Packet structure for response This entry indicates the number of drivers in the returned data
  • 16. short messageType; int responseSize; long driverId; double latitude; double longitude; float direction; Handler - Packet structure for response The rest of the lines repeat for every driver responseSize times and include the position data for every driver. Now that we understand the protocol lets dig into the code that implements it
  • 17. public class Handler extends BinaryWebSocketHandler { public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1; public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2; public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3; @Autowired private LocationService loc; @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { ByteBuffer b = message.getPayload(); short messageType = b.getShort(); short stringLength = b.getShort(); StringBuilder bld = new StringBuilder(); for(int iter = 0 ; iter < stringLength ; iter++) { bld.append((char)b.get()); } String token = bld.toString(); switch(messageType) { case MESSAGE_TYPE_LOCATION_UPDATE: Handler The handler class is a binary web socket handler that receives callbacks on incoming packets. Lets go over the code. These are constants used in the binary protocol to communicate the type of request or response.
  • 18. public class Handler extends BinaryWebSocketHandler { public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1; public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2; public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3; @Autowired private LocationService loc; @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { ByteBuffer b = message.getPayload(); short messageType = b.getShort(); short stringLength = b.getShort(); StringBuilder bld = new StringBuilder(); for(int iter = 0 ; iter < stringLength ; iter++) { bld.append((char)b.get()); } String token = bld.toString(); switch(messageType) { case MESSAGE_TYPE_LOCATION_UPDATE: Handler This is a callback for a binary messages from the client
  • 19. public class Handler extends BinaryWebSocketHandler { public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1; public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2; public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3; @Autowired private LocationService loc; @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { ByteBuffer b = message.getPayload(); short messageType = b.getShort(); short stringLength = b.getShort(); StringBuilder bld = new StringBuilder(); for(int iter = 0 ; iter < stringLength ; iter++) { bld.append((char)b.get()); } String token = bld.toString(); switch(messageType) { case MESSAGE_TYPE_LOCATION_UPDATE: Handler The API works with NIO's byte buffer which allows us to run through a request efficiently
  • 20. public class Handler extends BinaryWebSocketHandler { public static final short MESSAGE_TYPE_LOCATION_UPDATE = 1; public static final short MESSAGE_TYPE_DRIVER_POSITIONS = 2; public static final short MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS = 3; @Autowired private LocationService loc; @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception { ByteBuffer b = message.getPayload(); short messageType = b.getShort(); short stringLength = b.getShort(); StringBuilder bld = new StringBuilder(); for(int iter = 0 ; iter < stringLength ; iter++) { bld.append((char)b.get()); } String token = bld.toString(); switch(messageType) { case MESSAGE_TYPE_LOCATION_UPDATE: Handler We get the length of the user token string and the byte array. Again I used bytes instead of chars. Since the token is 100% ASCII we can rely on that
  • 21. String token = bld.toString(); switch(messageType) { case MESSAGE_TYPE_LOCATION_UPDATE: double lat = b.getDouble(); double lon = b.getDouble(); float dir = b.getFloat(); double radius = b.getDouble(); boolean seeking = b.get() == 1; loc.updateUserLocation(token, lat, lon, dir); List<UserDAO> response; short responseType; if(seeking) { response = loc.findAvailableDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_DRIVER_POSITIONS; } else { response = loc.findAllDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS; } if(response != null && response.size() > 0) { try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos);) { dos.writeShort(responseType); dos.writeInt(response.size()); Handler Assuming this is a location update we pull out the data and update the User object
  • 22. String token = bld.toString(); switch(messageType) { case MESSAGE_TYPE_LOCATION_UPDATE: double lat = b.getDouble(); double lon = b.getDouble(); float dir = b.getFloat(); double radius = b.getDouble(); boolean seeking = b.get() == 1; loc.updateUserLocation(token, lat, lon, dir); List<UserDAO> response; short responseType; if(seeking) { response = loc.findAvailableDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_DRIVER_POSITIONS; } else { response = loc.findAllDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS; } if(response != null && response.size() > 0) { try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos);) { dos.writeShort(responseType); dos.writeInt(response.size()); Handler We prepare to return a response based on the seeking flag, we also need to mark the response type correctly
  • 23. if(seeking) { response = loc.findAvailableDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_DRIVER_POSITIONS; } else { response = loc.findAllDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS; } if(response != null && response.size() > 0) { try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos);) { dos.writeShort(responseType); dos.writeInt(response.size()); for(UserDAO u : response) { dos.writeLong(u.getId()); dos.writeDouble(u.getLatitude()); dos.writeDouble(u.getLongitude()); dos.writeFloat(u.getDirection()); } dos.flush(); BinaryMessage bin = new BinaryMessage(bos.toByteArray()); session.sendMessage(bin); } } Handler I used a byte array output stream to construct the response, I use try with resources to close the streams automatically when I'm done. I just write out the response data to the steam
  • 24. if(seeking) { response = loc.findAvailableDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_DRIVER_POSITIONS; } else { response = loc.findAllDrivers(lat, lon, radius); responseType = MESSAGE_TYPE_AVAILBLE_DRIVER_POSITIONS; } if(response != null && response.size() > 0) { try(ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos);) { dos.writeShort(responseType); dos.writeInt(response.size()); for(UserDAO u : response) { dos.writeLong(u.getId()); dos.writeDouble(u.getLatitude()); dos.writeDouble(u.getLongitude()); dos.writeFloat(u.getDirection()); } dos.flush(); BinaryMessage bin = new BinaryMessage(bos.toByteArray()); session.sendMessage(bin); } } Handler And finally we convert the byte array data from the stream to a byte array then send to the client. This is it for the basic server code…