Add multiple SPIFFS files to captive portal on Async web server on ESP32 - webserver

I'm using simple code of modified Captive portal with asynchronous web portal (from ESPAsyncWebServer library - https://github.com/me-no-dev/ESPAsyncWebServer). It send html page from SPIFFS flash memory.
The way it is working now, is that it send index.html on any connection. I've just modified single line that in mentioned example was sending hmtl code. What I'd like to archive, is to be able to send more files, like html file and image.
So here is my code:
#include <DNSServer.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include "ESPAsyncWebServer.h"
#include <SPIFFS.h>
DNSServer dnsServer;
AsyncWebServer server(80);
class CaptiveRequestHandler : public AsyncWebHandler {
public:
CaptiveRequestHandler() {}
virtual ~CaptiveRequestHandler() {}
bool canHandle(AsyncWebServerRequest *request) {
//request->addInterestingHeader("ANY");
return true;
}
void handleRequest(AsyncWebServerRequest *request) {
request->send(SPIFFS, "/index.html", String(), false);
}
};
void setup() {
Serial.begin(115200);
if (!SPIFFS.begin()) {
Serial.println("An Error has occurred while mounting SPIFFS");
return;
}
WiFi.softAP("esp-captive");
dnsServer.start(53, "*", WiFi.softAPIP());
server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP
server.on("/image1", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, "/image1.jpg", "image/jpg"); // this part has been modified
});
server.begin();
}
void loop() {
dnsServer.processNextRequest();
}
I've tried to add
server.on("/image1", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(SPIFFS, "/image1.jpg", "image/jpg"); // this part has been modified
});
in setup section as explained here - https://randomnerdtutorials.com/display-images-esp32-esp8266-web-server/
But it's not working. I've tried messing with path changing "/" in places whare it appears, but with no luck. Further, if I change
void handleRequest(AsyncWebServerRequest *request) {
request->send(SPIFFS, "/index.html", String(), false);
}
to
void handleRequest(AsyncWebServerRequest *request) {
request->send(SPIFFS, "/image1.jpg", "image/jpg");
}
when logging to AP I get image not website, so I think paths are good.
To add more information this is my webpage code:
<!DOCTYPE html>
<html style="height: 100%">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body style="background-image: url('image1'); background-size: contain; background-color: black; background-repeat: no-repeat; background-position: 50% 0%; height=100%">
<h1 style="color:white">ESP32</h1>
</body>
</html>
and it is working fine on non-captive_portal solution (as explaind in previously mentioned tutorial).
So my question is how can I get to load not only single file on captive portal in asynchronous webserver, but more complicated (still very simple) webpage?

I struggeled with it for a while. But here is the answer. On the CaptiveRequestHandler() {} you can place your http calls.
Here is an example for you:
class CaptiveRequestHandler : public AsyncWebHandler {
public:
CaptiveRequestHandler() {
/* THIS IS WHERE YOU CAN PLACE THE CALLS */
server.onNotFound([](AsyncWebServerRequest *request){
AsyncWebServerResponse* response = request->beginResponse(SPIFFS, "/NotFound.html", "text/html");
request->send(response);
});
server.on("/Bootstrap.min.css", HTTP_GET, [](AsyncWebServerRequest *request) {
AsyncWebServerResponse* response = request->beginResponse(SPIFFS, "/Bootstrap.min.css", "text/css");
request->send(response);
});
}
virtual ~CaptiveRequestHandler() {}
bool canHandle(AsyncWebServerRequest *request) {
//request->addInterestingHeader("ANY");
return true;
}
void handleRequest(AsyncWebServerRequest *request) {
request->send(SPIFFS, "/index.html", String(), false);
}
};

Related

asp.net core with signalR < - > static socket

Use Case:
I have a asp.net core web application with signalR core for messaging. :)
Problem:
I have to receive messages from a socket connection [via System.Net.Sockets] (machine with own socket communication)
Is there any way to integrate the socket client in the web app (maybe Progamm.cs or Startup.cs?)
And how can I get access to the signalR to forward the received message to the signalR Hub?
thx
I suggest you to read the stockticker sample on : https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-server-broadcast-with-signalr
I show you here a small sample which you can adapt to your application. You have to subscribe the messages from your own socket communication and then you can forward this messages to the connected clients.
Here is a small sample how to send the time from server to the clients.
(The interesting part for you is the line GlobalHost.ConnectionManager.GetHubContext<ClockHub>().Clients.All.sendTime(DateTime.UtcNow.ToString());. Which this you can send something to all connected clients.
My main class is a clock which sends the actual time to all connected clients:
public class Clock
{
private static Clock _instance;
private Timer timer;
private Clock()
{
timer = new Timer(200);
timer.Elapsed += Timer_Elapsed;
timer.Start();
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{ // ---> This is the important part for you: Get hubContext where ever you use it and call method on hub GlobalHost.ConnectionManager.GetHubContext<ClockHub>().Clients.All.sendTime(DateTime.UtcNow.ToString());
GlobalHost.ConnectionManager.GetHubContext<ClockHub>().Clients.Clients()
}
public static Clock Instance
{
get
{
if (_instance == null)
{
_instance = new Clock();
}
return _instance;
}
}
}
}
In the startup I created a sigleton instance of this clock, which lives as long as the application is running.
public class Startup
{
public void Configuration(IAppBuilder app)
{
var inst = Clock.Instance;
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
}
My Hub:
public class ClockHub : Hub<IClockHub>
{
}
Hub interface which defines the method, which the server can call:
public interface IClockHub
{
void sendTime(string actualTime);
}
This is the clients part:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
</head>
<body>
<div id="timeLabel" ></div>
<script src="scripts/jquery-1.6.4.min.js"></script>
<script src="scripts/jquery.signalR-2.2.0.js"></script>
<script src="signalr/hubs"></script>
<script>
$(function () { // I use jQuery in this example
var ticker = $.connection.clockHub;
function init() {
}
ticker.client.sendTime = function (h) {
$("#timeLabel").html(h);
}
$.connection.hub.start().done(init);
});
</script>
</body>
</html>
How to inject hubcontext in asp.net core 2.x
Call SignalR Core Hub method from Controller

Vertx3.0 Simple Form upload

Vertx3.0 http simpleform file uploader is throwing error for multiple file.
Am using vertx3.0 simple form upload. It is working fine when i upload single file. If the form has the input "multiple" and choose multiple files, The HTTPServerUpload is throwing error "Response has already been written". Since the response is end in the endhandler for 1st file, it is throwing this error for subsequent files. is there any other way for multiple files ?
Simpleform file upload using vertx3.0
public class SimpleFormUploadServer extends AbstractVerticle {
public static void main(String[] args) {
Runner.runExample(SimpleFormUploadServer.class);
}
#Override
public void start() throws Exception {
vertx.createHttpServer()
.requestHandler(req -> {
if (req.uri().equals("/")) {
// Serve the index page
req.response().sendFile("index.html");
} else if (req.uri().startsWith("/form")) {
req.setExpectMultipart(true);
req.uploadHandler(upload -> {
upload.exceptionHandler(cause -> {
req.response().setChunked(true)
.end("Upload failed");
});
upload.endHandler(v -> {
req.response()
.setChunked(true)
.end("Successfully uploaded to "
+ upload.filename());
});
// FIXME - Potential security exploit! In a real
// system you must check this filename
// to make sure you're not saving to a place where
// you don't want!
// Or better still, just use Vert.x-Web which
// controls the upload area.
upload.streamToFileSystem(upload.filename());
});
} else {
req.response().setStatusCode(404);
req.response().end();
}
}).listen(8080);
}
}
Exception :
SEVERE: Unhandled exception
java.lang.IllegalStateException: Response has already been written
at io.vertx.core.http.impl.HttpServerResponseImpl.checkWritten(HttpServerResponseImpl.java:561)
at io.vertx.core.http.impl.HttpServerResponseImpl.end0(HttpServerResponseImpl.java:389)
at io.vertx.core.http.impl.HttpServerResponseImpl.end(HttpServerResponseImpl.java:307)
at io.vertx.core.http.impl.HttpServerResponseImpl.end(HttpServerResponseImpl.java:292)
at com.nokia.doas.vertx.http.upload.SimpleFormUploadServer$1$1$2.handle(SimpleFormUploadServer.java:85)
at com.nokia.doas.vertx.http.upload.SimpleFormUploadServer$1$1$2.handle(SimpleFormUploadServer.java:1)
at io.vertx.core.http.impl.HttpServerFileUploadImpl.notifyEndHandler(HttpServerFileUploadImpl.java:213)
at io.vertx.core.http.impl.HttpServerFileUploadImpl.lambda$handleComplete$165(HttpServerFileUploadImpl.java:206)
at io.vertx.core.file.impl.AsyncFileImpl.lambda$doClose$226(AsyncFileImpl.java:470)
at io.vertx.core.impl.ContextImpl.lambda$wrapTask$16(ContextImpl.java:335)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:358)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:357)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:112)
at java.lang.Thread.run(Unknown Source)
index.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title></title>
</head>
<body>
<form action="/form" ENCTYPE="multipart/form-data" method="POST" name="wibble">
choose a file to upload:<input type="file" name="files" multiple="multiple"/><br>
<input type="submit"/>
</form>
</body>
</html>
You can use vertx-web to easily handle file uploads:
router.route().handler(BodyHandler.create());
router.post("/some/path/uploads").handler(routingContext -> {
Set<FileUpload> uploads = routingContext.fileUploads();
// Do something with uploads....
});
Moreover, you will take benefits of the routing facility, and you can even serve static files such as index.html.
Hope this will help.
Multiple file upload is achievable in vert.x. Use multiple upload button in HTML and use uploadHandler of HttpRequest. UploadHandler would be invoked as many times any many files have been uploaded.
HttpServerRequest request = routingContext.request();
request.setExpectMultipart(true);
request.endHandler(new Handler<Void>() {
#Override
public void handle(Void aVoid) {
MultiMap entries = request.formAttributes();
Set<String> names = entries.names();
logger.info("UPLOAD_CONTENT: fileName = "+entries.get("fileName"));
logger.info("UPLOAD_CONTENT: type = "+entries.get("type"));
logger.info("UPLOAD_CONTENT: names = "+names);
request.response().setChunked(true).end(createResponse("SUCCESS"));
}
});
// This would be called multiple times
request.uploadHandler(upload -> {
upload.exceptionHandler(new Handler<Throwable>() {
#Override
public void handle(Throwable error) {
logger.error("UPLOAD_CONTENT: Error while uploading content "+upload.filename());
logger.error("UPLOAD_CONTENT: error = "+error.toString());
error.printStackTrace();
request.response().setChunked(true).end(createResponse("FAILURE"));
}
});
upload.endHandler(new Handler<Void>() {
#Override
public void handle(Void aVoid) {
logger.info("UPLOAD_CONTENT: fileName = "+upload.filename());
logger.info("UPLOAD_CONTENT: name = "+upload.name());
logger.info("UPLOAD_CONTENT: contentType = "+upload.contentType());
logger.info("UPLOAD_CONTENT: size = "+upload.size());
UtilityFunctions.uploadToS3(upload.filename(), "testfolder");
}
});
upload.streamToFileSystem(upload.filename());
});

Remove DIV from a webpage for webview android using Jsoup

I want to partially view a webpage on webview android and remove some div element from the webpage. I have a webpage like this
<!DOCTYPE html>
<body>
<div id="a"><p>Remove aa</p></div>
<div id="b"><p>bb</p></div>
</body></html>
Now I want to remove the div with id 'a' from the webpage.
I tried to code it with Jsoup but I am not well enough to make it out. Please see my full code:
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Bitmap;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class CustomWebsite extends Activity {
private WebView webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_website);
Document doc;
String htmlcode = "";
try {
doc = Jsoup.connect("http://skyasim.info/ab.html").get();
doc.head().getElementsByTag("DIV#a").remove();
htmlcode = doc.html();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
webView = (WebView) findViewById(R.id.webView_test);
webView.setWebViewClient(new myWebClient());
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("htmlcode");
}
public class myWebClient extends WebViewClient
{
#Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
// TODO Auto-generated method stub
super.onPageStarted(view, url, favicon);
}
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
// TODO Auto-generated method stub
view.loadUrl(url);
return true;
}
}
}
You can do this without using Jsoup you know. Just use plain old javascript. The following code will show how to remove an element from the HTML page and display the rest.
final WebView mWebView = (WebView) findViewById(R.id.mWebViewId);
mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url)
{
mWebView.loadUrl("javascript:(function() { " +
"document.getElementById('a')[0].style.display='none'; " +
"})()");
}
});
mWebView.loadUrl(youUrl);
Remove it from the document by selecting it and then using the remove-method.
doc.select("div#a").remove();
System.out.println(doc);
Example:
Document doc = Jsoup.parse(html);
System.out.println("Before removal of 'div id=\"a\"' = ");
System.out.println("-------------------------");
System.out.println(doc);
doc.select("div#a").remove();
System.out.println("\n\nAfter removal of 'div id=\"a\"' = ");
System.out.println("-------------------------");
System.out.println(doc);
will result in
Before removal of 'div id="a"' =
-------------------------
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="a">
<p>Remove aa</p>
</div>
<div id="b">
<p>bb</p>
</div>
</body>
</html>
After removal of 'div id="a"' =
-------------------------
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="b">
<p>bb</p>
</div>
</body>
</html>
I had tried to use Jsoup to do something similar before, but my app always crash. If you are open to using Javascript only (which helps to make your app size smaller), here is what I did for my app:
webview3.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
view.loadUrl("javascript:var con = document.getElementById('a'); " +
"con.style.display = 'none'; ");
}
});
Hope my Javascript is correct. The idea here is to use Javascript to hide the div after the page has finished loading.

how to add script to the body (similar functionality like method renderHead(Component component, IHeaderResponse response))

With the following code, I add trackingpixel to some page page.html by overriding the method renderHead(Component component, IHeaderResponse response). This works fine.
page.html looks like this:
<!doctype html>
<html xmlns:wicket="http://wicket.apache.org/">
<head>
..
<wicket:container wicket:id="header"></wicket:container>
</head>
<body>
..
<script wicket:id="scriptHolder" type="text/javascript" > I would like to add my script here
</script>
..
</body>
</html>
TrackingPixel.java:
public abstract class TrackingPixel extends AbstractDefaultAjaxBehavior {
protected TrackingPixel(TrackingPixelType type) {
..
}
#Override
public void renderHead(Component component, IHeaderResponse response) {
response.renderOnDomReadyJavaScript("WebtrekkInstance = {
..
'path' : 'anyPath',
...:...
..
};
");
}
}
renderHead-method adds a trackingpixel to the main page. Right mouse click on the page -> source code shows that the following script is added to the page:
<script type="text/javascript" >
Wicket.Event.add(window, "domready", function(event) {
WebtrekkInstance = {
..
'path' : 'anyPath',
...:...
..
};
..
;});
</script>
Now I would like to add trackingpixel to a popup. My problem is that I can't add a script to the body. The method renderHead(Component component, IHeaderResponse response) doesn't do that, because (I guess) the popup pops up on the same page, so there is only one head and it will not render twice. So I tried to do this with WebMarkupContainer as you can see below.
OurServicePopup.java
/**
* Class to display our service as popup
*/
public class OurServicePopupPage<T> extends WebPage {
public OurServicePopupPage(PageParameters parameters) {
super(parameters);
}
#Override
protected void onInitialize() {
add(new OurServicePixel());
super.onInitialize();
}
}
OurServicePixel.java looks like this:
public class OurServicePopupPixel extends TrackingPixel{
public OurServicePopupPixel() {
}
WebMarkupContainer scriptContainer = new WebMarkupContainer("scriptContainer");
#Override
public void renderHead(Component component, IHeaderResponse response) {
scriptContainer.add(new AttributeAppender("type", Model.of("text/javascript")));
scriptContainer.add(
new AttributeAppender("src","WebtrekkInstance = {
..
'path' : 'anyPath',
...:...
..
};
");
}
add(scriptContainer); //this shows error
}
The problem here is that I cannot add the scriptContainer. add(scriptContainer); will not work, because OurServicePopupPixel is a behaviour and not a page.
Maybe you can simple use a Label component with setEscapeModelStrings(false). But it seems a bit strange.
I didn't full understand what is your javascript doing, but maybe you can try to execute it when the DOM is ready. Using a renderHead like this:
public void renderHead(IHeaderResponse response) {
response.render(OnDomReadyHeaderItem.forScript( ... YOUR SCRIPT HERE ... ));
}
I hope it helps.

Finding out when a GWT module has loaded

I am exporting a GWT method to native javascript in the following manner:
public class FaceBookGalleryEntryPoint implements EntryPoint {
#Override
public void onModuleLoad() {
FacebookGallery facebookGallery = new FacebookGallery();
RootPanel.get().add(facebookGallery);
initLoadGallery(facebookGallery);
}
private native void initLoadGallery(FacebookGallery pl) /*-{
$wnd.loadGallery = function (galleryId) {
pl.#com.example.fbg.client.FacebookGallery::loadGallery(Ljava/lang/String;)(galleryId);
};
}-*/;
}
In the host page, I am trying to invoke it:
<html>
<head>
<title>Facebook image gallery</title>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
</head>
<body>
<script type="text/javascript" src="/fbg/fbg.nocache.js"></script>
<h1>Facebook gallery test</h1>
<script type="text/javascript">
$(document).ready(function() {
loadGallery('blargh');
});
</script>
</body>
</html>
Unfortunately, when the document.ready callback is invoked, the function is not yet defined. When manually executed from the Firebug console the function works just fine.
I could perform some polling every 50 milliseconds until I find a defined function by that name, but it seems like a horrible approach.
How can I get notified when the module is loaded and therefore when the function is available?
I would try to define a callback function in the hostpage and call it from GWT at the end of the onModuleLoad() method.
Hostpage function:
<script type="text/javascript">
function onGwtReady() {
loadGallery('blargh');
};
</script>
GWT:
public void onModuleLoad() {
FacebookGallery facebookGallery = new FacebookGallery();
RootPanel.get().add(facebookGallery);
initLoadGallery(facebookGallery);
// Using a deferred command ensures that notifyHostpage() is called after
// GWT initialisation is finished.
Scheduler.get().scheduleDeferred(new Command() {
public void execute() {
notifyHostpage();
}
}
}
private native void notifyHostpage() /*-{
$wnd.onGwtReady();
}-*/;