JaxRS 2 – HTML 5 SSE (Server Sent Events)
SSE (Server Sent Events), HTML5 teknolojisi içinde geliştirilen bir Web Pushing teknolojisidir. Peki Pushing nedir?
Pushing
Sunucu uygulama üzerinden belirli aralıklarla güncellenen veri kümelerinin, web tarayıcının talebi olmadan, sunucu – – – > tarayıcı yönünde iletilmesidir. Twitter güncellemelerinin web sayfasında belirli aralıklarla güncel olarak sunulması, Facebook paylaşımlarının yeni paylaşım oldukça ekranda belirmesi, anlık finansal verilerin (dolar kuru, parite vs..) anlık olarak kullanıcı ekranlarına sunulması, pushing uygulamalarına örnek verilebilir.
Server Sent Events, pushing için kullanılan tek teknik değildir elbette, bundan önceki şuradaki yazıda, LongPolling tekniğiyle nasıl web tarayıcıya veri yayılımı sağlancağından bahsetmiştik. Push teknolojilerini sıralama ile gösterirsek, sanırım şu sıralama yanlış olmaz;
Polling –> LongPolling –> ServerSent Events –> WebSocket
ServerSent Events, halihazırda geliştirimi devam edilen bir teknoloji, WebSocket teknolojisinde olduğu gibi. Bu sebeple her web tarayıcıda kullanılamayabileceğini ve elbette yeni nesil web tarayıcıları tarafından desteklendiğini belirtmek isterim.
Çalışma mantığı
SSE teknolojisinin çalışma mantığı aşağıdaki resimde görüldüğü gibi olmaktadır. Web tarayıcı tarafından, SSE desteklenen sunucuya bir el sıkışma isteği (handshake request) iletilir, sunucu sistem ise “text/event-stream” veri türünde bir el sıkışma yanıtı (handshake response) döndürür. Web tarayıcı ve SSE servisi tarafından el sıkışılan SSE söyleşmesiyle artık, sunucu istediği miktarda ve istediği anda veri kaynaklarını tarayıcı yönünde iletebilir.
Java EE 7 standart teknoloji şemsiyesi altında bulunan JaxRS 2 standardı, 2. versiyonuyla birlikte SSE (Server Sent Events) teknolojisine destek sunmaktadır. Şimdi ise Twitter REST Api kullanarak bir kelimeyi arama yapan ve elde ettiği sonuçları tüm istemcilere sunan bir Jax-RS 2 uygulamasını birlikte gerçekleştirelim.
Bağımlılıklar
Jax-RS 2, Java EE 7 standart kümesi altında bulunur ve referans uygulayıcı kütüphanesi Jersey 2‘ dir. Bu maksatla eğer uygulama Glassfish 4 uygulama sunucusunda koşturulmak isteniyorsa, bir Maven bağımlılığı olarak pom.xml konfigürasyon dosyasına eklenmelidir.
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
SSE teknolojisi, Jersey 2 kütüphanesinin harici bir özelliği olarak uygulamalara eklenebilmektedir. Yani Jersey 2 core kütüphanesinde SSE özelliği bulunmaz, bir eklenti/özellik biçiminde bir bağımlılık olarak eklenmelidir.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-sse</artifactId>
<version>2.0</version>
</dependency>
Uygulama içerisinde JSON nesne ve dizilerini kolaylıkla yönetmek adına yine Java EE 7 dahilindeki bir standart olan Json-P’ yi kullanacağız. Bu maksatla “javax.json” bağımlılığı pom.xml içerisinde eklenmelidir.
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.json</artifactId>
<version>1.0</version>
</dependency>
Uygulama bir Twitter araması gerçekleştireceği için, Twitter REST Api versiyon 1.1 kullanmak durumundadır. Bu maksatla, Twitter REST Api kullanımını esnekleştiren Twitter4j kütüphanesinden faydalanılabilir.
<dependency>
<groupId>org.twitter4j</groupId>
<artifactId>twitter4j-core</artifactId>
<version>3.0.3</version>
</dependency>
Eğer uygulama bir uygulama sunucusu haricinde Jetty veya Tomcat gibi tüğ siklet Servlet Konteyner ortamlarında çalıştırılması da isteniyorsa, pom.xml içerisinde “jersey-container-servlet” bağımlılığı da eklenebilir.
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.0</version>
</dependency>
Konfigürasyon
Bir Jax-RS sunucusuna giriş noktası için, uygulama içerisinde bir REST Endpoint sınıfı eklenmelidir.
@ApplicationPath("/search")
public class SearchEndpoint extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes=new HashSet<>();
classes.add(SearchResource.class);
return classes;
}
@Override
public Set<Object> getSingletons() {
Set<Object> singletons=new HashSet<>();
singletons.add(new SseFeature());
return singletons;
}
}
Application#getClasses() metoduna dikka kesildiğinde SearchResource isimli Java sınıfının bir RESTful kaynağı olarak yapılandırıldığı ve Application#getSingletons() metodunda ise SseFeature sınıfı üzerinden SSE özelliğinin RESTful uygulamasına tanıtıldığı görülmektedir.
SearchResource RESTful kaynağı
SearchResource isimli Java sınıfı, web tarayıcı-sunucu arasında SSE el sıkışmasını sağlayan ve sunucu üzerinde yapılan Twitter aramalarını kullanıcılara dağıtan (broadcast) bir REST kaynağıdır.
@Path("/")
public class SearchResource {
private static final SseBroadcaster BROADCASTER = new SseBroadcaster();
private static final ScheduledExecutorService sch = Executors.newSingleThreadScheduledExecutor();
static {
sch.scheduleWithFixedDelay(
new SearchTwitTask(BROADCASTER)
,0 // Twitter aramasına hemen başla (0 sn)
,30 // 30 saniyede bir SearchTwitTask'in run() metodunu koştur.
,TimeUnit.SECONDS);
}
@GET
@Produces("text/event-stream")
@Path("/hang")
public EventOutput getMessages() {
EventOutput eventOutput = new EventOutput();
BROADCASTER.add(eventOutput); // Dağıtım listesine ekleniyor
return eventOutput;
}
}
Jax-RS ile SSE teknolojisi üzerinden veri dağıtımı gerçekleştirilmek isteniyorsa bir SseBroadcaster nesnesi oluşturulmalıdır. Sunucuya istek yapan her bir client, bu SseBroadcaster nesnesine eklenirse, SseBroadcaster#broadcast(..) metodu üzerinden tüm kullanıcılara veri dağıtımı yapılabilir.
SseBroadcaster sınıfı her bir tekil kullanıcıyı EventOutput sınıfı türünden bir nesneyle içerisinde barındırır. Örneğin bir kullanıcı SearchResource#getMessages metoduna bir RESTful isteği yaptığında, yeni bir EventOutput nesnesi oluşturulmaktadır. Oluşturulan EventOutput nesnesi, o anki kullanıcıyı temsil eden bir dağıtım nesnesi durumundadır. getMessages() metodu üzerinde oluşturulan EventOutput nesnesi, dağıtıcı özellikli SseBroadcaster eklenir ve “text/event-stream” veri biçiminde kullanıcıya geri gönderilir. “text/event-stream” türünden Handshake verisini alan Web tarayıcı ise, sunucu tarafından verileri alabilir hale gelir.
30 saniyede bir arama yapılması ve kullanıcılara dağıtılması
public class SearchTwitTask implements Runnable {
private SseBroadcaster broadcaster;
public SearchTwitTask(SseBroadcaster broadcaster) {
this.broadcaster = broadcaster;
}
@Override
public void run() {
// JsonArray oluşturucu
JsonArrayBuilder jsonArray = Json.createArrayBuilder();
// JsonObject oluşturucu
JsonObjectBuilder jsonObject = Json.createObjectBuilder();
/* Twitter4J konfigürasyon ve arama */
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setDebugEnabled(true)
.setOAuthConsumerKey("kkkkkkkkkkkkk")
.setOAuthConsumerSecret("sssssssssssssss")
.setOAuthAccessToken("ttttttttttttttttttttt")
.setOAuthAccessTokenSecret("tttttttttttttttttttsssssssssssss");
Twitter twitter = new TwitterFactory(cb.build()).getInstance();
QueryResult result = null;
try {
// Java EE ile ilgili twit'ler aranıyor.
result = twitter.search(new Query("Java EE"));
// Dönen kayıtların çeşitli bilgileri birer birer JsonObject nesnesine ekleniyor.
for (Status status : result.getTweets()) {
jsonObject.add("created_at",
status.getCreatedAt().toString());
jsonObject.add("from_user_name",
status.getUser().getScreenName());
jsonObject.add("profile_image_url",
status.getUser().getProfileImageURL());
// JsonObject nesnesi son olarak bir JsonArray nesnesine ekleniyor.
jsonArray.add(jsonObject.add("text",
status.getText()));
}
} catch (TwitterException e) {
e.printStackTrace();
} /* Twitter4J konfigürasyon ve arama */
// Dönen bilgiler Json dizisi olarak elde ediliyor
String twitJsonArray= jsonArray.build().toString();
// Kullanıcılara dağıtılacak bir olay bildirim nesnesi oluşturuluyor
OutboundEvent.Builder b = new OutboundEvent.Builder();
// Veri "application/json" olarak gönderilecek
b.mediaType(MediaType.APPLICATION_JSON_TYPE);
// Veri dağıtıcıya eklenmiş tüm kullanıcılara dağıtılıyor.
broadcaster.broadcast(b.data(String.class,twitJsonArray).build());
}
}
SearchTwitTask sınıfı Runnable arayüzü türünden, 30 saniye aralıklarla SchedulerExecutorService nesnesi ile koşturulan bir Görev sınıfıdır. @Override edilen Runnable#run metodunda sırasıyla önce “Java EE” ifadesi geçen Twitter kayıtları aranmakta ve elde edilen kayıtlar Json-P kütüphanesiyle JsonArray nesnesi olarak elde edilmektedir.
Jax-RS 2 ile yapılacak broadcast işlemleri geliştirici tarafından oluşturulacak OutboundEvent nesnesi olarak kullanıcılara iletilebilmektedir. SearchTwitTask sınıfında, MIME biçimi “application/json” olarak tanımlanan String türünden twitJsonArray verisi, SseBroadcaster sınıfının broadcast metodu üzerinden tüm uygulama tüketicilerine dağıtılmaktadır.
SSE HTML tarafı
<!DOCTYPE html>
<html class=" js no-touch svg inlinesvg svgclippaths no-ie8compat js no-touch svg inlinesvg svgclippaths no-ie8compat">
<head>
<title></title>
<meta charset="UTF-8">
<link type="text/css" rel="stylesheet" href="http://cdn.jsdelivr.net/foundation/4.0.4/css/foundation.min.css">
<link type="text/css" rel="stylesheet" href="http://cdn.jsdelivr.net/foundation/4.0.4/css/normalize.css">
</head>
<body>
<!-- Header and Nav -->
<div class="row">
<div class="large-12 columns">
<div class="panel">
<div class="row">
<div class="large-6 columns">
<h1>Twitter Search Broadcaster</h1>
</div>
<div class="large-6 columns" >
Pushes new data searhed for Java EE in every 30 seconds.
</div>
</div>
</div>
</div>
</div>
<!-- End Header and Nav -->
<div class="row" id="alertbox" style="display: none;">
<div class="large-12 columns">
<span class="alert-box alert">Your browser doesn not support HTML 5 SSE :(</span>
</div>
</div>
<div class="row">
<!-- Feed Placeholder -->
<div id="feeds" class="large-12 columns"></div>
</div>
<!-- Footer -->
<footer class="row">
<div class="large-12 columns">
<hr>
<div class="row">
<div class="large-12 columns">
<p>© Twitter Search Broadcaster | kodedu.com</p>
</div>
</div>
</div>
</footer>
<script type="text/x-mustache-template" id="feedTemplate">
{{#.}}
<!-- Feed Entry -->
<div class="row">
<div class="large-1 columns small-3"><img style="min-height: 60px;min-width: 60px;" src="{{profile_image_url}}">
</div>
<div class="large-11 columns">
<div class="row">
<div class="large-12 columns">
<p><strong>{{from_user_name}} said:</strong> {{text}}</p>
</div>
<div class="large-12 columns">
<p><strong>Created date:</strong> {{created_at}}</p>
</div>
</div>
</div>
</div>
<!-- End Feed Entry -->
<hr>
{{/.}}
</script>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.7.2/mustache.min.js"></script>
<script type="text/javascript" src="resources/jaxrs-sse.js"></script>
</body>
</html>
Twitter araması yapılan uygulamanın, HTML tarafında aşağıdaki teknolojiler kullanılmaktadır.
- Foundation Zurb: Twitter Bootstrap alternatifi
- Jquery: Herkesin bildiği şey 🙂
- Mustache.js: Basit bir Template çözümü
SSE Javascript tarafı
(function () {
if((typeof EventSource)==="undefined")
{
// SSE desteklenmiyorsa
$("#alertbox").css("display","");
return;
}
// SSE servisine bağlan
var eventSource = new EventSource("search/hang");
// SSE Handshake sağlandığında
eventSource.onopen = function (e) {
console.log("Waiting message..");
};
// Hata olduğunda
eventSource.onerror = function (e) {
console.log("Error");
console.log(e);
};
// Mesaj geldiğinde
eventSource.onmessage=function (e) {
console.log(e);
var feedTemplate = $("#feedTemplate").html();
var feeds = Mustache.render(feedTemplate, $.parseJSON(e.data));
// Gelen Json veri template ile render edilir ve ekrana basılır
$("#feeds").html(feeds);
};
})();
HTML 5 SSE teknolojisinin Javascript taraflı ana nesnesi EventSource’ dir. EventSource nesnesi oluşturulur oluşturulmaz, SSE servisine bir URL ile istekte bulunmakta ve bağlanma durumuna, hata durumuna ve mesaj gelme durumlarını yönetmek için bulunan onopen, onerror, onmessage alanlarına bağlı Callback fonsiyonlarını işletmektedir.
Uygulamanın çalıştırılması
Twitter araması uygulaması, iki yollar çalıştırılabilir.
1. Yol (Gömülü jetty eklentisi ile)
> mvn clean package jetty:run-war
2. yol (Glassfish 4 uygulama sunucusu ile)
> mvn clean package > asadmin start-domain > asadmin deploy JaxRS-SSE-1.0.war
İki yöntemin biriyle çalıştırılan uygulamlara http://localhost:8080/JaxRS-SSE adresinden erişebilirsiniz.
Canlı Örnek: http://discoverjavaee.com/JaxRS-SSE/
Uygulama kodlarına: https://github.com/rahmanusta/JaxRS-SSE adresinden erişebilirsiniz.
Tekrar görüşmek dileğiyle.
Tag:backend, HTML 5, javaee7, jax-rs 2, server sent events