diff --git a/My-Shop/README.md b/My-Shop/README.md new file mode 100644 index 0000000..c0a3a37 --- /dev/null +++ b/My-Shop/README.md @@ -0,0 +1 @@ +A Sample Shopping Website (for training purposes) diff --git a/My-Shop/go-backend/set-go-path.sh b/My-Shop/go-backend/set-go-path.sh new file mode 100644 index 0000000..8a704b2 --- /dev/null +++ b/My-Shop/go-backend/set-go-path.sh @@ -0,0 +1,8 @@ +# You have to source this file from your shell ! +# +# . set-go-path.sh +# + +export GOPATH="$PWD" +echo "GOPATH=$GOPATH" + diff --git a/My-Shop/go-backend/src/.gitignore b/My-Shop/go-backend/src/.gitignore new file mode 100644 index 0000000..002c01e --- /dev/null +++ b/My-Shop/go-backend/src/.gitignore @@ -0,0 +1 @@ +code.google.com diff --git a/My-Shop/go-backend/src/itix.fr/rest-backend/main.go b/My-Shop/go-backend/src/itix.fr/rest-backend/main.go new file mode 100644 index 0000000..607c802 --- /dev/null +++ b/My-Shop/go-backend/src/itix.fr/rest-backend/main.go @@ -0,0 +1,208 @@ +package main + +import ( + "code.google.com/p/gorest" + "net/http" + "fmt" + "strings" + "io/ioutil" + "encoding/json" +) + +type Category struct { + Id string + Name string +} + +var Categories []Category = []Category { + { "fringues", "Habillage" }, + { "cuisine", "Cuisine" }, + { "digital", "Digital" }, + { "maison", "Bricolage" } } + +type Product struct { + Id int + Name string + Category string + Image string + Description string + Price float32 + Stock int + VendorId string + VendorName string + VendorProductId string + IsDigital bool +} + +type BuyResponse struct { + ResponseCode string + DownloadUrl string +} + +type CallbackResponse struct { + ResponseCode string `json:"code"` + RedirectUrl string `json:"redirect_url"` +} + +type BuyCallback struct { + VendorId string + VendorName string + VendorProductId string +} + +var Products []Product = []Product { + { 0, "T-Shirt", "fringues", "brice.jpg", "Le T-Shirt de Brice de Nice.", 99.9, 1, "", "", "", false }, + { 1, "Pull col roulé", "fringues", "pull.jpg", "Un pull à col roulé de couleur marron.", 2.00, 10, "", "", "", false }, + { 2, "Cocotte minute", "cuisine", "cocotte.jpg", "La cocotte minute 'Presto'.", 45.00, 2, "", "", "", false }, + { 3, "Marteau-Piqueur", "maison", "mp.jpg", "Le marteau piqueur 'DESTRUCTOR 2000'.", 600, 5, "", "", "", false } } +// { 4, "Visseuse Ultrasonique", "maison", "visseuse.jpg", "La visseuse-dévisseuse de chez Méga Store.", 600, 5, "mega-store", "Méga Store", "0001", false }, +// { 5, "DVD de Harry Poter", "digital", "dvd.jpg", "L'histoire de Ari l'empotteur au pays des merveilles.", 29.9, -1, "zouba-books", "Zouba Books", "12345", true } } + +func main() { + gorest.RegisterService(new(MyShopService)) // Register our service + http.Handle("/api/",gorest.Handle()) + http.Handle("/", http.FileServer(http.Dir("www-root"))) + http.ListenAndServe(":8787", nil) +} + +// REST Service Definition +type MyShopService struct { + gorest.RestService `root:"/api/" consumes:"application/json" produces:"application/json"` + getCategories gorest.EndPoint `method:"GET" path:"/shop/category/" output:"[]Category"` + getProductsByCategory gorest.EndPoint `method:"GET" path:"/shop/product/?{category:string}" output:"[]Product"` + searchProducts gorest.EndPoint `method:"GET" path:"/shop/search/{criteria:string}" output:"[]Product"` + getProduct gorest.EndPoint `method:"GET" path:"/shop/product/{id:int}" output:"Product"` + addProduct gorest.EndPoint `method:"POST" path:"/market/product/" postdata:"Product"` + buyProduct gorest.EndPoint `method:"GET" path:"/shop/product/{id:int}/buy" output:"BuyResponse"` +} + +func(serv MyShopService) SearchProducts(criteria string) []Product { + fmt.Println(">>> SearchProducts: criteria = ", criteria) + if (criteria == "") { + return Products + } + + sliceofcriteria := strings.Split(criteria, " ") + + FilteredProducts := []Product {} + for _, p := range Products { + var selected bool = false + name := strings.ToLower(p.Name) + desc := strings.ToLower(p.Description) + + for _, c := range sliceofcriteria { + c = strings.ToLower(c) + if strings.Contains(name, c) || strings.Contains(desc, c) { + selected = true + } + } + + if selected { + FilteredProducts = append(FilteredProducts, p) + } + } + + return FilteredProducts +} + +func(serv MyShopService) GetProducts() []Product { + fmt.Println(">>> GetProducts") + return Products +} + +func(serv MyShopService) BuyProduct(id int) (resp BuyResponse) { + fmt.Println(">>> BuyProduct: id = ", id) + if id > len(Products) - 1 { + serv.ResponseBuilder().SetResponseCode(404).Overide(true) + return + } + + if Products[id].Stock == 0 { + serv.ResponseBuilder().SetResponseCode(409).Overide(true) + return + } + + if Products[id].Stock >0 { + Products[id].Stock-- + } + + return_code := "order-accepted" + redirect_url := "" + if Products[id].VendorId != "" { + elements := []string { "http://api.the-vendor.test:8080/api/vendor", Products[id].VendorId, "callback", Products[id].VendorProductId, "1" } + callback := strings.Join(elements, "/") + fmt.Println(">>> BuyProduct: firing callback to vendor", Products[id].VendorName, "with URL =", callback) + + client := &http.Client{} + req, err := http.NewRequest("GET", callback, nil) + resp, err := client.Do(req) + if err != nil { + fmt.Println(">>> BuyProduct: ERROR", err) + return_code = "error" + serv.ResponseBuilder().SetResponseCode(500).Overide(true) + } else { + defer resp.Body.Close() + fmt.Println(">>> BuyProduct: response Status:", resp.Status) + if resp.Status != "200 OK" { + body, _ := ioutil.ReadAll(resp.Body) + fmt.Println(">>> BuyProduct: response Body:", string(body)) + return_code = "error" + serv.ResponseBuilder().SetResponseCode(500).Overide(true) + } else { + if Products[id].IsDigital { + fmt.Println(">>> BuyProduct: Decoding JSON response") + json_resp := new(CallbackResponse) + json.NewDecoder(resp.Body).Decode(json_resp) + redirect_url = json_resp.RedirectUrl; + } else { + fmt.Println(">>> BuyProduct: Ignoring JSON response") + } + } + } + } + + fmt.Println(">>> BuyProduct: return_code =", return_code, "redirect_url =", redirect_url) + + resp = BuyResponse { return_code, redirect_url } + return +} + + +func(serv MyShopService) GetProduct(id int) (p Product) { + fmt.Println(">>> GetProduct: id =", id) + if id > len(Products) - 1 { + serv.ResponseBuilder().SetResponseCode(404).Overide(true) + return + } + p = Products[id] + return +} + +func(serv MyShopService) AddProduct(posted Product) { + fmt.Println(">>> AddProduct: posted =", posted) + id := len(Products) + posted.Id = id + Products = append(Products, posted); + serv.ResponseBuilder().Created("/api/shop/product/"+string(id)) +} + +func(serv MyShopService) GetProductsByCategory(category string) []Product { + fmt.Println(">>> GetProductsByCategory: category =", category) + if (category == "") { + return Products + } + + FilteredProducts := []Product {} + for _, p := range Products { + if p.Category == category { + FilteredProducts = append(FilteredProducts, p) + } + } + + return FilteredProducts +} + +func(serv MyShopService) GetCategories() []Category { + fmt.Println(">>> GetCategories") + return Categories +} diff --git a/My-Shop/go-backend/test/create-product.sh b/My-Shop/go-backend/test/create-product.sh new file mode 100644 index 0000000..afe7268 --- /dev/null +++ b/My-Shop/go-backend/test/create-product.sh @@ -0,0 +1,6 @@ +curl -H "Content-Type: application/json" \ + -d '{ "Name": "A New Product", "Category": "maison", "Image": "logo.png", "Description": "My brand new product", "Price": 1, "Stock": 1 }' \ + -X POST \ + -D - \ + http://localhost:8787/api/shop/product/ + diff --git a/My-Shop/go-backend/www-root/.gitignore b/My-Shop/go-backend/www-root/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/My-Shop/go-backend/www-root/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/My-Shop/go-backend/www-root/book-1234.pdf b/My-Shop/go-backend/www-root/book-1234.pdf new file mode 100644 index 0000000..2a7ae62 Binary files /dev/null and b/My-Shop/go-backend/www-root/book-1234.pdf differ diff --git a/My-Shop/go-backend/www-root/img/bluray.jpg b/My-Shop/go-backend/www-root/img/bluray.jpg new file mode 100644 index 0000000..c6d0085 Binary files /dev/null and b/My-Shop/go-backend/www-root/img/bluray.jpg differ diff --git a/My-Shop/go-backend/www-root/img/brice.jpg b/My-Shop/go-backend/www-root/img/brice.jpg new file mode 100644 index 0000000..70e382c Binary files /dev/null and b/My-Shop/go-backend/www-root/img/brice.jpg differ diff --git a/My-Shop/go-backend/www-root/img/cocotte.jpg b/My-Shop/go-backend/www-root/img/cocotte.jpg new file mode 100644 index 0000000..a59ef98 Binary files /dev/null and b/My-Shop/go-backend/www-root/img/cocotte.jpg differ diff --git a/My-Shop/go-backend/www-root/img/dvd.jpg b/My-Shop/go-backend/www-root/img/dvd.jpg new file mode 100644 index 0000000..966dd50 Binary files /dev/null and b/My-Shop/go-backend/www-root/img/dvd.jpg differ diff --git a/My-Shop/go-backend/www-root/img/livre.jpg b/My-Shop/go-backend/www-root/img/livre.jpg new file mode 100644 index 0000000..f2b9bad Binary files /dev/null and b/My-Shop/go-backend/www-root/img/livre.jpg differ diff --git a/My-Shop/go-backend/www-root/img/logo.png b/My-Shop/go-backend/www-root/img/logo.png new file mode 100644 index 0000000..e0bb104 Binary files /dev/null and b/My-Shop/go-backend/www-root/img/logo.png differ diff --git a/My-Shop/go-backend/www-root/img/mp.jpg b/My-Shop/go-backend/www-root/img/mp.jpg new file mode 100644 index 0000000..d4a4e96 Binary files /dev/null and b/My-Shop/go-backend/www-root/img/mp.jpg differ diff --git a/My-Shop/go-backend/www-root/img/pull.jpg b/My-Shop/go-backend/www-root/img/pull.jpg new file mode 100644 index 0000000..845a1d6 Binary files /dev/null and b/My-Shop/go-backend/www-root/img/pull.jpg differ diff --git a/My-Shop/go-backend/www-root/img/visseuse.jpg b/My-Shop/go-backend/www-root/img/visseuse.jpg new file mode 100644 index 0000000..34ea520 Binary files /dev/null and b/My-Shop/go-backend/www-root/img/visseuse.jpg differ diff --git a/My-Shop/go-backend/www-root/index.html b/My-Shop/go-backend/www-root/index.html new file mode 100644 index 0000000..b34f6fb --- /dev/null +++ b/My-Shop/go-backend/www-root/index.html @@ -0,0 +1,31 @@ + + + + My Shop + + + + + + +
+ +
+
+
+

Boutique

+
Subtitle
+
+ +
+ +
+
+
+
+
+
+ + diff --git a/My-Shop/go-backend/www-root/js/.gitignore b/My-Shop/go-backend/www-root/js/.gitignore new file mode 100644 index 0000000..3a3b630 --- /dev/null +++ b/My-Shop/go-backend/www-root/js/.gitignore @@ -0,0 +1,3 @@ +dojo +dojox +dijit diff --git a/My-Shop/go-backend/www-root/js/shop.js b/My-Shop/go-backend/www-root/js/shop.js new file mode 100644 index 0000000..eebd7d4 --- /dev/null +++ b/My-Shop/go-backend/www-root/js/shop.js @@ -0,0 +1,217 @@ +var apibase = "/api/shop"; +var dialog; + +require([ "dojo/ready", "dojo/request/xhr", "dojo/dom-construct", "dojo/dom", "dojo/on", "dojo/dom-style", "dojo/dom-attr", "dijit/Dialog" ], + function(ready, xhr, domConstruct, dom, on, domStyle, domAttr, Dialog) { + + function setOnCategoryClickHandler(node, catid, catname) { + on(node, "click", function() { + loadProducts(catid); + setSubTitle(catname); + }); + } + + function setOnProductClickHandler(node, id) { + on(node, "click", function() { + displayProduct(id); + }); + } + + function setSearchHandler() { + on(dom.byId("search_textbox"), "keyup", doSearch); + on(dom.byId("search_textbox"), "blur", function () { + console.log("SEARCH TEXT BOX >> Blur"); + window.setInterval(function () { console.log("SEARCH TEXT BOX >> Je la cache"); domStyle.set("search_results", "visibility", "hidden"); }, 100); + }); + on(dom.byId("search_textbox"), "focus", function () { + console.log("SEARCH TEXT BOX >> Focus"); + var searchCriteria = domAttr.get("search_textbox", "value"); + if (searchCriteria != "") { + domStyle.set("search_results", "visibility", "visible"); + } + }); + } + + function setBuyHandler(node, id) { + on(node, "click", function() { + buyProduct(id); + }); + } + + function buyProduct(id) { + xhr(apibase + "/product/" + encodeURI(id) + "/buy", + { handleAs: "json" } + ).then(function (data) { + dialog.set("title", "Commande acceptée"); + dialog.set("content", "La commande est partie. Vous allez très prochainement recevoir le produit."); + console.log("test..."); + if (data.DownloadUrl != null && data.DownloadUrl != "") { + console.log("popup !"); + window.location.href = data.DownloadUrl; + } + dialog.show(); + displayProduct(id); // Refresh UI + }, function (err) { + if (err.response != null && err.response.status == 409) { + dialog.set("title", "Erreur"); + dialog.set("content", "Le produit n'est plus en stock. Désolé."); + dialog.show(); + } else { + dialog.set("title", "OOPS"); + dialog.set("content", "Erreur interne. Désolé."); + dialog.show(); + } + console.log(err); + displayProduct(id); // Refresh UI + }, function (evt) { + + }); + } + + function doSearch(evt) { + var searchCriteria = domAttr.get("search_textbox", "value"); + if (searchCriteria == "") { + domStyle.set("search_results", "visibility", "hidden"); + return; + } else { + domStyle.set("search_results", "visibility", "visible"); + } + + xhr(apibase + "/search/" + encodeURI(searchCriteria), + { handleAs: "json" } + ).then(function (data) { + domConstruct.empty("search_results"); + var placeholder = dom.byId("search_results"); + for (var i = 0; i < data.length; i++) { + var div = domConstruct.create("div", {}, placeholder); + domConstruct.create("img", { width: "32px", src: "/img/" + data[i].Image }, div); + domConstruct.create("span", { textContent: data[i].Name }, div); + setOnProductClickHandler(div, data[i].Id); + } + if (data.length == 0) { + domConstruct.create("span", { textContent: "Aucun résultat", 'class': "no_result" }, placeholder); + } + }, function (err) { + console.log(err); + }, function (evt) { + + }); + } + + function displayProduct(id) { + xhr(apibase + "/product/" + encodeURI(id), + { handleAs: "json" } + ).then(function (data) { + domConstruct.empty("products_pane"); + var placeholder = dom.byId("products_pane"); + var div = dojo.create("div", { 'class': 'product_detail' }, placeholder); + dojo.create("h1", { 'class': "product_name", textContent: data.Name }, div); + var div2 = dojo.create("div", {}, div) + dojo.create("img", { src: "/img/" + data.Image, height: "200px" }, div2); + dojo.create("span", { 'class': "product_price", textContent: data.Price + " €" }, div2); + if (data.Stock != "-1") { + dojo.create("span", { 'class': "product_stock", textContent: "En Stock: " + data.Stock }, div2); + } + if (data.VendorName != "") { + dojo.create("span", { 'class': "sold_by", textContent: "Vendu par: " + data.VendorName }, div2); + } + + var buy_button = dojo.create("div", { 'class': "buy_button" }, div2); + dojo.create("div", { textContent: "Acheter !" }, buy_button); + setBuyHandler(buy_button, data.Id); + + dojo.create("div", { 'class': "product_description", textContent: data.Description }, div); + }, function (err) { + if (err.response != null && err.response.status == 404) { + dialog.set("title", "Erreur"); + dialog.set("content", "Le produit a été retiré de la vente. Désolé."); + dialog.show(); + } else { + dialog.set("title", "OOPS"); + dialog.set("content", "Erreur interne. Désolé."); + dialog.show(); + } + console.log(err); + }, function (evt) { + + }); + } + + function setSubTitle(name) { + domConstruct.empty("subtitle"); + dom.byId("subtitle").textContent = name; + } + + function loadProducts(catid) { + var queryString = ""; + if (catid != null) { + queryString = "?category=" + encodeURI(catid); + } + xhr(apibase + "/product/" + queryString, + { handleAs: "json" } + ).then(function (data) { + domConstruct.empty("products_pane"); + var placeholder = dom.byId("products_pane"); + var table = domConstruct.create("table", { 'class': "product_table" }, placeholder); + var current_tr = null; + var i = 0; + for (; i < data.length; i++) { + if (i % 3 == 0) { + current_tr = domConstruct.create("tr", {}, table); + } + var td = domConstruct.create("td", {}, current_tr); + domConstruct.create("img", { width: "100px", src: "/img/" + data[i].Image }, td); + domConstruct.create("span", { textContent: data[i].Name }, td); + setOnProductClickHandler(td, data[i].Id); + } + // Fill remaining columns if less than 3 products + for (; i < 3; i++) { + domConstruct.create("td", {}, current_tr); + } + }, function (err) { + dialog.set("title", "OOPS"); + dialog.set("content", "Erreur interne. Désolé."); + dialog.show(); + console.log(err); + }, function (evt) { + + }); + } + + function loadCategories() { + xhr(apibase + "/category/", + { handleAs: "json" } + ).then(function (data) { + var placeholder = dom.byId("category_list_placeholder"); + domConstruct.empty("category_list_placeholder"); + var all_products_node = domConstruct.create("span", { textContent: "Tous les produits", 'class': "category_item" }, placeholder); + setOnCategoryClickHandler(all_products_node, null, "Tous les produits"); + for (var i = 0; i < data.length; i++) { + var catid = data[i].Id; + var catname = data[i].Name; + var node = domConstruct.create("span", { textContent: data[i].Name, 'class': "category_item" }, placeholder); + setOnCategoryClickHandler(node, catid, catname); + } + }, function (err) { + dialog.set("title", "OOPS"); + dialog.set("content", "Erreur interne. Désolé."); + dialog.show(); + console.log(err); + }, function (evt) { + + }); + } + + ready(function() { + loadCategories(); + loadProducts(null); + setSubTitle("Tous les produits"); + setSearchHandler(); + dialog = new Dialog({ + id: "global_dialog", + title: "...", + content: "...", + style: "width: 500px; display: none;" + }); + }); +}); diff --git a/My-Shop/go-backend/www-root/style.css b/My-Shop/go-backend/www-root/style.css new file mode 100644 index 0000000..bb12160 --- /dev/null +++ b/My-Shop/go-backend/www-root/style.css @@ -0,0 +1,218 @@ +#search_bar { + position: absolute; + top: 0px; + right: 0px; + width: 450px; + height: 50px; +} + +#search_textbox { + width: 350px; + height: 20px; + position: absolute; + top: 0px; + right: 0px; + background-color: gray; +} + +#search_results { + width: 350px; + position: absolute; + top: 30px; + right: 0px; + background-color: white; + border: 1px solid lightgrey; + z-index: 255; + visibility: hidden; +} + +#search_results span, #search_results img { + display:inline-block; + vertical-align:middle; + margin-right: 5px; +} + +#search_results div { + cursor: pointer; cursor: hand; + border: 1px solid white; +} + +#search_results div:hover { + border: 1px solid gray; +} + +.no_result { + color: lightgrey; + font-style: italic; +} + +#category_bar { + position: absolute; + top: 100px; + left: 0px; + width: 200px; +} + +#products_pane { + position: absolute; + top: 100px; + left: 250px; + right: 0px; + min-height: 300px; +} + +#content_pane { + position: absolute; + top: 0px; + left: 30px; + right: 30px; +} + +body { + background-color: #AAAAAA; + font-family: Sans-Serif; +} + +#main_pane { + position: relative; + width: 80%; + margin: auto; + background-color: #EEEEEE; + min-height: 800px; +} + +#site_header { + position: absolute; + top: 0px; + height: 116px; + left: 30px; + right: 30px; +} + +#site_logo { + position: absolute; + top: 40px; + width: 345px; + left: 40px; +} + + +#pages_container { + position: absolute; + top: 140px; + bottom: 30px; + right: 30px; + left: 60px; +} + +#page_title { + position: absolute; + top: 10px; + right: 30px; + left: 30px; + font: normal normal normal 25px/1.2em sans-serif; +} + +#page_title > h2 { + line-height: 1.1em; + font: normal normal normal 22px/1.1em sans-serif; + color: #00CCFF; + margin : 0; + padding : 0; + border : 0; + outline : 0; +} + +#page_title > h5 { + color: #7F7F7F; + line-height: 1.2em; + letter-spacing: normal; + margin : 0; + padding : 0; + border : 0; + outline : 0; +} + +.product_table { + width: 100%; + border-spacing: 10px; + border-collapse: separate; +} + +.product_table span, .product_table img { + display: block; + margin: auto; +} + +.product_table img { + margin-bottom: 10px; +} + +.product_table td { + padding-top: 10px; + padding-bottom: 10px; + text-align: center; + background-color: white; + width: 33%; + color: darkgrey; + cursor: pointer; cursor: hand; +} + +.category_item { + display: block; + margin-bottom: 10px; + text-decoration: none; + cursor: pointer; cursor: hand; +} + +.category_item:hover { + text-decoration: underline; +} + +.product_price { + position: absolute; + top: 20px; + left: 300px; + color: red; + font-size: x-large; +} + +.product_detail > div { + position: relative; +} + +.product_stock { + position: absolute; + top: 60px; + left: 300px; + color: darkgreen; +} + +.sold_by { + position: absolute; + top: 80px; + left: 300px; + color: darkgreen; +} + +.product_description { + margin-top: 20px; + color: darkgrey; +} + +.buy_button { + position: absolute; + top: 150px; + left: 300px; + background-color: red; + color: white; + width: 100px; + height: 30px; + cursor: pointer; cursor: hand; +} + +.buy_button > div { + text-align: center; + line-height: 30px; + vertical-align: middle; +} diff --git a/My-Shop/soap-backend/deploy.sh b/My-Shop/soap-backend/deploy.sh new file mode 100755 index 0000000..05ad388 --- /dev/null +++ b/My-Shop/soap-backend/deploy.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +(cd src && javac fr/itix/soapbackend/*.java) + +BASE=tomcat/webapps/axis2/WEB-INF/services/ +NAME=VendorBackend +mkdir -p build/$NAME/META-INF build/$NAME/fr/itix/soapbackend/ +cp src/fr/itix/soapbackend/*.class build/$NAME/fr/itix/soapbackend/ +cp src/services.xml build/$NAME/META-INF/ + +cp -rv build/$NAME $BASE/ + + diff --git a/My-Shop/soap-backend/src/fr/itix/soapbackend/.gitignore b/My-Shop/soap-backend/src/fr/itix/soapbackend/.gitignore new file mode 100644 index 0000000..6b468b6 --- /dev/null +++ b/My-Shop/soap-backend/src/fr/itix/soapbackend/.gitignore @@ -0,0 +1 @@ +*.class diff --git a/My-Shop/soap-backend/src/fr/itix/soapbackend/VendorBackend.java b/My-Shop/soap-backend/src/fr/itix/soapbackend/VendorBackend.java new file mode 100644 index 0000000..3405c85 --- /dev/null +++ b/My-Shop/soap-backend/src/fr/itix/soapbackend/VendorBackend.java @@ -0,0 +1,14 @@ +package fr.itix.soapbackend; + +public class VendorBackend { + public String NotifySale(String productId, int number, String callerID) { + if ("12345".equals(productId) && "zouba-books".equals(callerID)) { + return "OK;http://online-shop.zouba-books.test:8787/book-1234.pdf"; + } + + if ("0001".equals(productId) && "mega-store".equals(callerID)) { + return "OK;"; + } + throw new RuntimeException("Unknown caller id or wrong product id"); + } +} diff --git a/My-Shop/soap-backend/src/services.xml b/My-Shop/soap-backend/src/services.xml new file mode 100644 index 0000000..d6863cb --- /dev/null +++ b/My-Shop/soap-backend/src/services.xml @@ -0,0 +1,16 @@ + + + Vendor Backend + + + + + + + fr.itix.soapbackend.VendorBackend + + diff --git a/WebAPI-Samples/LICENSE b/WebAPI-Samples/LICENSE new file mode 100644 index 0000000..1885bc4 --- /dev/null +++ b/WebAPI-Samples/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Nicolas MASSE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/WebAPI-Samples/README.md b/WebAPI-Samples/README.md new file mode 100644 index 0000000..56bf5d6 --- /dev/null +++ b/WebAPI-Samples/README.md @@ -0,0 +1,12 @@ +## WebAPI-Samples +Sample Code for various Web API Protocols (SOAP, REST, etc.) + +### SOAP samples +- [MTOM Attachments in Java](./SOAP/MTOM/) + +### REST samples +- TODO + +### Utils +- [A Reverse Proxy written in GO](./Utils/ReverseProxy/) + diff --git a/WebAPI-Samples/SOAP/MTOM/.gitignore b/WebAPI-Samples/SOAP/MTOM/.gitignore new file mode 100644 index 0000000..a6387dd --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/.gitignore @@ -0,0 +1 @@ +tomcat diff --git a/WebAPI-Samples/SOAP/MTOM/README.md b/WebAPI-Samples/SOAP/MTOM/README.md new file mode 100644 index 0000000..562889c --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/README.md @@ -0,0 +1,29 @@ +# MTOM Sample Server Code + +## Introduction to MTOM + +MTOM is a standard to attach a file to a SOAP message (others way to do so are: MIME Attachments or in-line base64). + +For a good introduction to MTOM read : + - http://www.mkyong.com/webservices/jax-ws/jax-ws-attachment-with-mtom/ + - https://axis.apache.org/axis2/java/core/docs/mtom-guide.html + - http://stackoverflow.com/questions/215741/how-does-mtom-work + +### How to know when a SOAP Message use MTOM ? + +The easy way : + - The HTTP request is a mime multipart +``` +POST /path/to/ws HTTP/1.1 +Content-type: multipart/related; start="bla"; type="application/xop+xml"; boundary="uuid:bla..."; +``` + - The SOAP Payload contains `Include` elements in the namespace `http://www.w3.org/2004/08/xop/include` +``` + +``` + +## MTOM Server Code Setup + +See [the documentation](./doc/README.md). + diff --git a/WebAPI-Samples/SOAP/MTOM/build.sh b/WebAPI-Samples/SOAP/MTOM/build.sh new file mode 100755 index 0000000..6a46096 --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/build.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +NAME=SoapBackend + +function die() { + echo "ERROR: $@" + exit 1 +} + +cd src || die "No source code" +javac $(find . -iname *.java) || die "Compilation error" +jar cvf ../build/$NAME.aar $(find . -iname *.class -or -iname *.xml) || die "Cannot build jar" + + diff --git a/WebAPI-Samples/SOAP/MTOM/build/.gitignore b/WebAPI-Samples/SOAP/MTOM/build/.gitignore new file mode 100644 index 0000000..6d81fcd --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/build/.gitignore @@ -0,0 +1 @@ +*.aar diff --git a/WebAPI-Samples/SOAP/MTOM/doc/README.md b/WebAPI-Samples/SOAP/MTOM/doc/README.md new file mode 100644 index 0000000..6008c28 --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/doc/README.md @@ -0,0 +1,104 @@ +## MTOM Server Code Setup + +### Pre-requisites + +To make this sample code work, you need: + - Tomcat (tested against version 7) + - Axis2 (tested against version 1.6) + +Get tomcat7 and install it in a `tomcat` folder. +``` +$ wget http://www.eu.apache.org/dist/tomcat/tomcat-7/v7.0.62/bin/apache-tomcat-7.0.62.tar.gz && tar zxvf apache-tomcat-*.tar.gz && mv apache-tomcat-*/ tomcat +``` + +Get axis2 and install the axis2.jar in `tomcat/webapps/axis2` + +``` +$ cd tomcat/webapps +$ wget http://www.eu.apache.org/dist//axis/axis2/java/core/1.6.2/axis2-1.6.2-war.zip && unzip axis2-1.6.2-war.zip axis2.war +$ cd .. +$ ./bin/startup.sh +``` + +### Build + +Build the .aar archive and deploy it. + +![build the .aar archive](./build.png) + +### Change the Axis Configuration to enable MTOM + +Edit the Axis2 configuration file: `tomcat/webapps/axis2/WEB-INF/conf/axis2.xml` and set the `enableMTOM` parameter to `true`. + +![set "enableMTOM" to "true"](./enable-mtom.png) + +**DO NOT FORGET TO RESTART TOMCAT !!!** + +## Test the MTOM attachment + +### Retrieve the WSDL + +Make sure Tomcat is started and the .aar archive is deployed. Then go to http://localhost:8080/axis2/services/listServices. + +Click on the `MTOMService` to get the WSDL. + +*Note:* make sure you save the file in its original format (File > Save As...). **Do not use copy paste that may break the XML format.** + +Just in case, you can find a copy of the WSDL [here](../wsdl/MtomService.wsdl). + + +### Test in SOAP UI + +1. Get SOAP UI (tested with version 5) +2. Import the WSDL +3. Open the auto-generated request for the `MtomServiceSoap12Binding` binding + ![](./binding.png) +4. Make sure `Enable MTOM` and `Force MTOM` in the bottom left pane are set to `true`. + ![](./mtom-soapui.png) +5. Import your attachment in the bottom pane. + ![](./attach-soapui.png) +6. Make sure to reference the attachment in your SOAP message using `cid:attachment-name`. + ![](./soap-soapui.png) +7. Make sure the attachement part in the bottom pane is set and the attachment type is set to `XOP` + ![](./attach-soapui.png) +8. Fire the request and observe the result ! + ![](./soap-result.png) + +Note: by clicking on the raw button in the request pane, you can have a look at the complete HTTP request. + +``` +POST http://localhost:8080/axis2/services/MtomService.MtomServiceHttpSoap12Endpoint/ HTTP/1.1 +Accept-Encoding: gzip,deflate +Content-Type: multipart/related; type="application/xop+xml"; start=""; start-info="application/soap+xml"; action="urn:countBytes"; boundary="----=_Part_1_223849750.1433937520698" +MIME-Version: 1.0 +Content-Length: 912 +Host: localhost:8080 +Connection: Keep-Alive +User-Agent: Apache-HttpClient/4.1.1 (java 1.5) + + +------=_Part_1_223849750.1433937520698 +Content-Type: application/xop+xml; charset=UTF-8; type="application/soap+xml"; action="countBytes" +Content-Transfer-Encoding: 8bit +Content-ID: + + + + + + + + + + +------=_Part_1_223849750.1433937520698 +Content-Type: text/plain; charset=us-ascii; name=my-attachment.txt +Content-Transfer-Encoding: 7bit +Content-ID: +Content-Disposition: attachment; name="my-attachment.txt"; filename="my-attachment.txt" + +Hello World ! + +------=_Part_1_223849750.1433937520698-- +``` + diff --git a/WebAPI-Samples/SOAP/MTOM/doc/attach-soapui.png b/WebAPI-Samples/SOAP/MTOM/doc/attach-soapui.png new file mode 100644 index 0000000..fd56e93 Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/attach-soapui.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/doc/axis-services.png b/WebAPI-Samples/SOAP/MTOM/doc/axis-services.png new file mode 100644 index 0000000..92db5d8 Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/axis-services.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/doc/binding.png b/WebAPI-Samples/SOAP/MTOM/doc/binding.png new file mode 100644 index 0000000..fa0af7a Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/binding.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/doc/build.png b/WebAPI-Samples/SOAP/MTOM/doc/build.png new file mode 100644 index 0000000..9269968 Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/build.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/doc/enable-mtom.png b/WebAPI-Samples/SOAP/MTOM/doc/enable-mtom.png new file mode 100644 index 0000000..2fe218b Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/enable-mtom.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/doc/mtom-soapui.png b/WebAPI-Samples/SOAP/MTOM/doc/mtom-soapui.png new file mode 100644 index 0000000..4aee2d5 Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/mtom-soapui.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/doc/soap-result.png b/WebAPI-Samples/SOAP/MTOM/doc/soap-result.png new file mode 100644 index 0000000..78276d2 Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/soap-result.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/doc/soap-soapui.png b/WebAPI-Samples/SOAP/MTOM/doc/soap-soapui.png new file mode 100644 index 0000000..50f946e Binary files /dev/null and b/WebAPI-Samples/SOAP/MTOM/doc/soap-soapui.png differ diff --git a/WebAPI-Samples/SOAP/MTOM/src/META-INF/services.xml b/WebAPI-Samples/SOAP/MTOM/src/META-INF/services.xml new file mode 100644 index 0000000..cab236d --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/src/META-INF/services.xml @@ -0,0 +1,16 @@ + + + SOAP Backend that exposes an MTOM enabled service + + + + + + + fr.itix.soapbackend.MTOMService + + diff --git a/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/.gitignore b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/.gitignore new file mode 100644 index 0000000..6b468b6 --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/.gitignore @@ -0,0 +1 @@ +*.class diff --git a/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/MTOMService.java b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/MTOMService.java new file mode 100644 index 0000000..5ea0378 --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/src/fr/itix/soapbackend/MTOMService.java @@ -0,0 +1,16 @@ +package fr.itix.soapbackend; + +import javax.jws.WebMethod; +import javax.jws.WebService; +import javax.xml.ws.soap.MTOM; + +@MTOM +@WebService(name="MtomPortType", + serviceName="MtomService", + targetNamespace="http://itix.fr/soap/mtom") +public class MTOMService { + @WebMethod + public int countBytes(byte[] bytes) { + return bytes.length; + } +} diff --git a/WebAPI-Samples/SOAP/MTOM/wsdl/MtomService.wsdl b/WebAPI-Samples/SOAP/MTOM/wsdl/MtomService.wsdl new file mode 100644 index 0000000..ccc8588 --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/wsdl/MtomService.wsdl @@ -0,0 +1,81 @@ + + + VendorBackend + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/WebAPI-Samples/SOAP/MTOM/wsdl/README.md b/WebAPI-Samples/SOAP/MTOM/wsdl/README.md new file mode 100644 index 0000000..bd21348 --- /dev/null +++ b/WebAPI-Samples/SOAP/MTOM/wsdl/README.md @@ -0,0 +1 @@ +This a sample WSDL, generated by Axis diff --git a/WebAPI-Samples/Utils/ReverseProxy/README.md b/WebAPI-Samples/Utils/ReverseProxy/README.md new file mode 100644 index 0000000..e634d9f --- /dev/null +++ b/WebAPI-Samples/Utils/ReverseProxy/README.md @@ -0,0 +1,27 @@ +## Reverse Proxy written in GO +This reverse proxy listens on a local port and forward all requests to a named host. It honors the proxy environment variables. + +### Initial Need + +Once upon a time, I had to circumvent a bug in a product that could not handle correctly an HTTPS connection to a proxy. + +Since it was an HTTPS connection, I could not setup a transparent proxy. + +### What it does + +It opens a local port and listen to HTTP requests, forwards the requests to a named host and send back the response. + +### How to use it + +```bash +go run src/itix.fr/forward/main.go -local-port 8080 -target https://www.opentrust.com +curl -D - http://localhost:8080/robots.txt +``` + +If you want to go through a proxy, do not forget to set the ```http_proxy``` and ```https_proxy``` variables ! + +```bash +export http_proxy=http://my.proxy:8888/ +export https_proxy=http://my.proxy:8888/ +``` + diff --git a/WebAPI-Samples/Utils/ReverseProxy/set-go-path.sh b/WebAPI-Samples/Utils/ReverseProxy/set-go-path.sh new file mode 100644 index 0000000..0c57277 --- /dev/null +++ b/WebAPI-Samples/Utils/ReverseProxy/set-go-path.sh @@ -0,0 +1,3 @@ +export GOPATH="$PWD" +echo "GOPATH=$GOPATH" + diff --git a/WebAPI-Samples/Utils/ReverseProxy/src/itix.fr/forward/main.go b/WebAPI-Samples/Utils/ReverseProxy/src/itix.fr/forward/main.go new file mode 100644 index 0000000..a2e7056 --- /dev/null +++ b/WebAPI-Samples/Utils/ReverseProxy/src/itix.fr/forward/main.go @@ -0,0 +1,96 @@ +package main + +import ( + "os" + "fmt" + "flag" + "net/http" + "net/http/httputil" + "net/url" +) + +// The local port on which we should listen to +var local_port int + +// The target URL on which we should redirect requests +var target string + +func init() { + // --local-port=9009 + flag.IntVar(&local_port, "local-port", 9090, "the TCP port to listen to") + + // --target=http://www.perdu.com + flag.StringVar(&target, "target", "http://www.perdu.com", "the target URL to redirect the request to") +} + +// The MyResponseWriter is a wrapper around the standard http.ResponseWriter +// We need it to retrieve the http status code of an http response +type MyResponseWriter struct { + Underlying http.ResponseWriter + Status int +} + +func (mrw *MyResponseWriter) Header() http.Header { + return mrw.Underlying.Header() +} + +func (mrw *MyResponseWriter) Write(b []byte) (int, error) { + return mrw.Underlying.Write(b) +} + +func (mrw *MyResponseWriter) WriteHeader(s int) { + mrw.Status = s + mrw.Underlying.WriteHeader(s) +} + +func main() { + // Parse the command line arguments + flag.Parse() + + // Parse the target URL and perform some sanity checks + url, err := url.Parse(target) + if err != nil { + panic(err) + } + + // Initialize a Reverse Proxy object with a custom director + proxy := httputil.NewSingleHostReverseProxy(url) + underlying_director := proxy.Director + proxy.Director = func(req *http.Request) { + // Let the underlying director do the mandatory job + underlying_director(req) + + // Custom Handling + // --------------- + // + // Filter out the "Host" header sent by the client + // otherwise the target server won't be able to find the + // matching virtual host. The correct host header will be + // added automatically by the net/http package. + req.Host = "" + } + + http.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { + // Log the incoming request (including headers) + fmt.Printf("%v %v HTTP/1.1\n", req.Method, req.URL) + req.Header.Write(os.Stdout) + fmt.Println() + + // Wrap the standard response writer with our own + // implementation because we need the status code of the + // response and that field is not exported by default + mrw := &MyResponseWriter{ Underlying: rw } + + // Let the reverse proxy handle the request + proxy.ServeHTTP(mrw, req) + + // Log the response + fmt.Printf("%v %v\n", mrw.Status, http.StatusText(mrw.Status)) + mrw.Header().Write(os.Stdout) + fmt.Println() + }) + + fmt.Printf("Listening on port %v for incoming requests...\n", local_port) + http.ListenAndServe(fmt.Sprintf(":%v", local_port), nil) +} +