Having trouble decrypting HTTPS traffic from IOS using FiddlerCore - ios

I was able to run the FiddlerCore demo (that comes with the package) without issue. I see both http and https traffic being logged on my PC.
My goal now is to do the same for my iOS traffic but I can't figure out what I am missing. I can see my https traffic fine when I use the desktop Fiddler app, by following the instructions at ConfigureForiOS.
I run the console FiddlerCore demo, hit 't' to trust the root certificate and then try to follow the same steps on my iPhone as I did for the Fidder app, namely setting my proxy to the Fiddler instance (my machine's IP and port 7777 as that is what it looks like the demo is using) and trusting the Fiddler cert that I had already installed on my phone when setting it up to work with the desktop Fiddler app. Then when I try to start an app on my phone that goes over https (for example a game) it just hangs. I don't see any errors being logged in the console app. It works ok when just running the desktop Fiddler app.
My SSL/cert/Fiddler knowledge is weak so I am hoping I am just missing a simple step or two.
Questions:
How can I capture iOS HTTPS traffic using the FiddlerCore demo app?
Do I need to trust the root certificate each time I start the demo
app (hitting 't')?
Thanks.
P.S. I added the demo app here, which can be found in the FiddlerCore package, for reference.
using Fiddler;
using System;
using System.Collections.Generic;
using System.Threading;
namespace FiddlerCoreDemo
{
class Program
{
static Proxy oSecureEndpoint;
static string sSecureEndpointHostname = "localhost";
static int iSecureEndpointPort = 7777;
public static void WriteCommandResponse(string s)
{
ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine(s);
Console.ForegroundColor = oldColor;
}
public static void DoQuit()
{
WriteCommandResponse("Shutting down...");
if (null != oSecureEndpoint) oSecureEndpoint.Dispose();
Fiddler.FiddlerApplication.Shutdown();
Thread.Sleep(500);
}
private static string Ellipsize(string s, int iLen)
{
if (s.Length <= iLen) return s;
return s.Substring(0, iLen - 3) + "...";
}
#if SAZ_SUPPORT
private static void ReadSessions(List<Fiddler.Session> oAllSessions)
{
Session[] oLoaded = Utilities.ReadSessionArchive(Environment.GetFolderPath(Environment.SpecialFolder.Desktop)
+ Path.DirectorySeparatorChar + "ToLoad.saz", false);
if ((oLoaded != null) && (oLoaded.Length > 0))
{
oAllSessions.AddRange(oLoaded);
WriteCommandResponse("Loaded: " + oLoaded.Length + " sessions.");
}
}
private static void SaveSessionsToDesktop(List<Fiddler.Session> oAllSessions)
{
bool bSuccess = false;
string sFilename = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
+ Path.DirectorySeparatorChar + DateTime.Now.ToString("hh-mm-ss") + ".saz";
try
{
try
{
Monitor.Enter(oAllSessions);
string sPassword = null;
Console.WriteLine("Password Protect this Archive (Y/N)?");
ConsoleKeyInfo oCKI = Console.ReadKey();
if ((oCKI.KeyChar == 'y') || (oCKI.KeyChar == 'Y'))
{
Console.WriteLine("\nEnter the password:");
sPassword = Console.ReadLine();
Console.WriteLine(String.Format("\nEncrypting with Password: '{0}'", sPassword));
}
Console.WriteLine();
bSuccess = Utilities.WriteSessionArchive(sFilename, oAllSessions.ToArray(), sPassword, false);
}
finally
{
Monitor.Exit(oAllSessions);
}
WriteCommandResponse( bSuccess ? ("Wrote: " + sFilename) : ("Failed to save: " + sFilename) );
}
catch (Exception eX)
{
Console.WriteLine("Save failed: " + eX.Message);
}
}
#endif
private static void WriteSessionList(List<Fiddler.Session> oAllSessions)
{
ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.White;
Console.WriteLine("Session list contains...");
try
{
Monitor.Enter(oAllSessions);
foreach (Session oS in oAllSessions)
{
Console.Write(String.Format("{0} {1} {2}\n{3} {4}\n\n", oS.id, oS.oRequest.headers.HTTPMethod, Ellipsize(oS.fullUrl, 60), oS.responseCode, oS.oResponse.MIMEType));
}
}
finally
{
Monitor.Exit(oAllSessions);
}
Console.WriteLine();
Console.ForegroundColor = oldColor;
}
static void Main(string[] args)
{
List<Fiddler.Session> oAllSessions = new List<Fiddler.Session>();
// <-- Personalize for your Application, 64 chars or fewer
Fiddler.FiddlerApplication.SetAppDisplayName("FiddlerCoreDemoApp");
#region AttachEventListeners
//
// It is important to understand that FiddlerCore calls event handlers on session-handling
// background threads. If you need to properly synchronize to the UI-thread (say, because
// you're adding the sessions to a list view) you must call .Invoke on a delegate on the
// window handle.
//
// If you are writing to a non-threadsafe data structure (e.g. List<t>) you must
// use a Monitor or other mechanism to ensure safety.
//
// Simply echo notifications to the console. Because Fiddler.CONFIG.QuietMode=true
// by default, we must handle notifying the user ourselves.
Fiddler.FiddlerApplication.OnNotification += delegate (object sender, NotificationEventArgs oNEA) { Console.WriteLine("** NotifyUser: " + oNEA.NotifyString); };
Fiddler.FiddlerApplication.Log.OnLogString += delegate (object sender, LogEventArgs oLEA) { Console.WriteLine("** LogString: " + oLEA.LogString); };
Fiddler.FiddlerApplication.BeforeRequest += delegate (Fiddler.Session oS)
{
// Console.WriteLine("Before request for:\t" + oS.fullUrl);
// In order to enable response tampering, buffering mode MUST
// be enabled; this allows FiddlerCore to permit modification of
// the response in the BeforeResponse handler rather than streaming
// the response to the client as the response comes in.
oS.bBufferResponse = false;
Monitor.Enter(oAllSessions);
oAllSessions.Add(oS);
Monitor.Exit(oAllSessions);
// Set this property if you want FiddlerCore to automatically authenticate by
// answering Digest/Negotiate/NTLM/Kerberos challenges itself
// oS["X-AutoAuth"] = "(default)";
/* If the request is going to our secure endpoint, we'll echo back the response.
Note: This BeforeRequest is getting called for both our main proxy tunnel AND our secure endpoint,
so we have to look at which Fiddler port the client connected to (pipeClient.LocalPort) to determine whether this request
was sent to secure endpoint, or was merely sent to the main proxy tunnel (e.g. a CONNECT) in order to *reach* the secure endpoint.
As a result of this, if you run the demo and visit https://localhost:7777 in your browser, you'll see
Session list contains...
1 CONNECT http://localhost:7777
200 <-- CONNECT tunnel sent to the main proxy tunnel, port 8877
2 GET https://localhost:7777/
200 text/html <-- GET request decrypted on the main proxy tunnel, port 8877
3 GET https://localhost:7777/
200 text/html <-- GET request received by the secure endpoint, port 7777
*/
if ((oS.oRequest.pipeClient.LocalPort == iSecureEndpointPort) && (oS.hostname == sSecureEndpointHostname))
{
oS.utilCreateResponseAndBypassServer();
oS.oResponse.headers.SetStatus(200, "Ok");
oS.oResponse["Content-Type"] = "text/html; charset=UTF-8";
oS.oResponse["Cache-Control"] = "private, max-age=0";
oS.utilSetResponseBody("<html><body>Request for httpS://" + sSecureEndpointHostname + ":" + iSecureEndpointPort.ToString() + " received. Your request was:<br /><plaintext>" + oS.oRequest.headers.ToString());
}
};
/*
// The following event allows you to examine every response buffer read by Fiddler. Note that this isn't useful for the vast majority of
// applications because the raw buffer is nearly useless; it's not decompressed, it includes both headers and body bytes, etc.
//
// This event is only useful for a handful of applications which need access to a raw, unprocessed byte-stream
Fiddler.FiddlerApplication.OnReadResponseBuffer += new EventHandler<RawReadEventArgs>(FiddlerApplication_OnReadResponseBuffer);
*/
/*
Fiddler.FiddlerApplication.BeforeResponse += delegate(Fiddler.Session oS) {
// Console.WriteLine("{0}:HTTP {1} for {2}", oS.id, oS.responseCode, oS.fullUrl);
// Uncomment the following two statements to decompress/unchunk the
// HTTP response and subsequently modify any HTTP responses to replace
// instances of the word "Microsoft" with "Bayden". You MUST also
// set bBufferResponse = true inside the beforeREQUEST method above.
//
//oS.utilDecodeResponse(); oS.utilReplaceInResponse("Microsoft", "Bayden");
};*/
Fiddler.FiddlerApplication.AfterSessionComplete += delegate (Fiddler.Session oS)
{
//Console.WriteLine("Finished session:\t" + oS.fullUrl);
Console.Title = ("Session list contains: " + oAllSessions.Count.ToString() + " sessions");
};
// Tell the system console to handle CTRL+C by calling our method that
// gracefully shuts down the FiddlerCore.
//
// Note, this doesn't handle the case where the user closes the window with the close button.
// See http://geekswithblogs.net/mrnat/archive/2004/09/23/11594.aspx for info on that...
//
Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);
#endregion AttachEventListeners
string sSAZInfo = "NoSAZ";
#if SAZ_SUPPORT
sSAZInfo = Assembly.GetAssembly(typeof(Ionic.Zip.ZipFile)).FullName;
// You can load Transcoders from any different assembly if you'd like, using the ImportTranscoders(string AssemblyPath)
// overload.
//
//if (!FiddlerApplication.oTranscoders.ImportTranscoders(Assembly.GetExecutingAssembly()))
//{
// Console.WriteLine("This assembly was not compiled with a SAZ-exporter");
//}
DNZSAZProvider.fnObtainPwd = () =>
{
Console.WriteLine("Enter the password (or just hit Enter to cancel):");
string sResult = Console.ReadLine();
Console.WriteLine();
return sResult;
};
FiddlerApplication.oSAZProvider = new DNZSAZProvider();
#endif
Console.WriteLine(String.Format("Starting {0} ({1})...", Fiddler.FiddlerApplication.GetVersionString(), sSAZInfo));
// For the purposes of this demo, we'll forbid connections to HTTPS
// sites that use invalid certificates. Change this from the default only
// if you know EXACTLY what that implies.
Fiddler.CONFIG.IgnoreServerCertErrors = false;
// ... but you can allow a specific (even invalid) certificate by implementing and assigning a callback...
// FiddlerApplication.OnValidateServerCertificate += new System.EventHandler<ValidateServerCertificateEventArgs>(CheckCert);
FiddlerApplication.Prefs.SetBoolPref("fiddler.network.streaming.abortifclientaborts", true);
// For forward-compatibility with updated FiddlerCore libraries, it is strongly recommended that you
// start with the DEFAULT options and manually disable specific unwanted options.
FiddlerCoreStartupFlags oFCSF = FiddlerCoreStartupFlags.Default;
// E.g. If you want to add a flag, start with the .Default and "OR" the new flag on:
// oFCSF = (oFCSF | FiddlerCoreStartupFlags.CaptureFTP);
// ... or if you don't want a flag in the defaults, "and not" it out:
// Uncomment the next line if you don't want FiddlerCore to act as the system proxy
// oFCSF = (oFCSF & ~FiddlerCoreStartupFlags.RegisterAsSystemProxy);
// *******************************
// Important HTTPS Decryption Info
// *******************************
// When FiddlerCoreStartupFlags.DecryptSSL is enabled, you must include either
//
// MakeCert.exe
//
// *or*
//
// CertMaker.dll
// BCMakeCert.dll
//
// ... in the folder where your executable and FiddlerCore.dll live. These files
// are needed to generate the self-signed certificates used to man-in-the-middle
// secure traffic. MakeCert.exe uses Windows APIs to generate certificates which
// are stored in the user's \Personal\ Certificates store. These certificates are
// NOT compatible with iOS devices which require specific fields in the certificate
// which are not set by MakeCert.exe.
//
// In contrast, CertMaker.dll uses the BouncyCastle C# library (BCMakeCert.dll) to
// generate new certificates from scratch. These certificates are stored in memory
// only, and are compatible with iOS devices.
// Uncomment the next line if you don't want to decrypt SSL traffic.
// oFCSF = (oFCSF & ~FiddlerCoreStartupFlags.DecryptSSL);
// NOTE: In the next line, you can pass 0 for the port (instead of 8877) to have FiddlerCore auto-select an available port
int iPort = 8877;
Fiddler.FiddlerApplication.Startup(iPort, oFCSF);
FiddlerApplication.Log.LogFormat("Created endpoint listening on port {0}", iPort);
FiddlerApplication.Log.LogFormat("Starting with settings: [{0}]", oFCSF);
FiddlerApplication.Log.LogFormat("Gateway: {0}", CONFIG.UpstreamGateway.ToString());
Console.WriteLine("Hit CTRL+C to end session.");
// We'll also create a HTTPS listener, useful for when FiddlerCore is masquerading as a HTTPS server
// instead of acting as a normal CERN-style proxy server.
oSecureEndpoint = FiddlerApplication.CreateProxyEndpoint(iSecureEndpointPort, true, sSecureEndpointHostname);
if (null != oSecureEndpoint)
{
FiddlerApplication.Log.LogFormat("Created secure endpoint listening on port {0}, using a HTTPS certificate for '{1}'", iSecureEndpointPort, sSecureEndpointHostname);
}
bool bDone = false;
do
{
Console.WriteLine("\nEnter a command [C=Clear; L=List; G=Collect Garbage; W=write SAZ; R=read SAZ;\n\tS=Toggle Forgetful Streaming; T=Trust Root Certificate; Q=Quit]:");
Console.Write(">");
ConsoleKeyInfo cki = Console.ReadKey();
Console.WriteLine();
switch (Char.ToLower(cki.KeyChar))
{
case 'c':
Monitor.Enter(oAllSessions);
oAllSessions.Clear();
Monitor.Exit(oAllSessions);
WriteCommandResponse("Clear...");
FiddlerApplication.Log.LogString("Cleared session list.");
break;
case 'd':
FiddlerApplication.Log.LogString("FiddlerApplication::Shutdown.");
FiddlerApplication.Shutdown();
break;
case 'l':
WriteSessionList(oAllSessions);
break;
case 'g':
Console.WriteLine("Working Set:\t" + Environment.WorkingSet.ToString("n0"));
Console.WriteLine("Begin GC...");
GC.Collect();
Console.WriteLine("GC Done.\nWorking Set:\t" + Environment.WorkingSet.ToString("n0"));
break;
case 'q':
bDone = true;
DoQuit();
break;
case 'r':
#if SAZ_SUPPORT
ReadSessions(oAllSessions);
#else
WriteCommandResponse("This demo was compiled without SAZ_SUPPORT defined");
#endif
break;
case 'w':
#if SAZ_SUPPORT
if (oAllSessions.Count > 0)
{
SaveSessionsToDesktop(oAllSessions);
}
else
{
WriteCommandResponse("No sessions have been captured");
}
#else
WriteCommandResponse("This demo was compiled without SAZ_SUPPORT defined");
#endif
break;
case 't':
try
{
WriteCommandResponse("Result: " + Fiddler.CertMaker.trustRootCert().ToString());
}
catch (Exception eX)
{
WriteCommandResponse("Failed: " + eX.ToString());
}
break;
// Forgetful streaming
case 's':
bool bForgetful = !FiddlerApplication.Prefs.GetBoolPref("fiddler.network.streaming.ForgetStreamedData", false);
FiddlerApplication.Prefs.SetBoolPref("fiddler.network.streaming.ForgetStreamedData", bForgetful);
Console.WriteLine(bForgetful ? "FiddlerCore will immediately dump streaming response data." : "FiddlerCore will keep a copy of streamed response data.");
break;
}
} while (!bDone);
}
/*
/// <summary>
/// This callback allows your code to evaluate the certificate for a site and optionally override default validation behavior for that certificate.
/// You should not implement this method unless you understand why it is a security risk.
/// </summary>
static void CheckCert(object sender, ValidateServerCertificateEventArgs e)
{
if (null != e.ServerCertificate)
{
Console.WriteLine("Certificate for " + e.ExpectedCN + " was for site " + e.ServerCertificate.Subject + " and errors were " + e.CertificatePolicyErrors.ToString());
if (e.ServerCertificate.Subject.Contains("fiddler2.com"))
{
Console.WriteLine("Got a certificate for fiddler2.com. We'll say this is also good for any other site, like https://fiddlertool.com.");
e.ValidityState = CertificateValidity.ForceValid;
}
}
}
*/
/*
// This event handler is called on every socket read for the HTTP Response. You almost certainly don't want
// to add a handler for this event, but the code below shows how you can use it to mess up your HTTP traffic.
static void FiddlerApplication_OnReadResponseBuffer(object sender, RawReadEventArgs e)
{
// NOTE: arrDataBuffer is a fixed-size array. Only bytes 0 to iCountOfBytes should be read/manipulated.
//
// Just for kicks, lowercase every byte. Note that this will obviously break any binary content.
for (int i = 0; i < e.iCountOfBytes; i++)
{
if ((e.arrDataBuffer[i] > 0x40) && (e.arrDataBuffer[i] < 0x5b))
{
e.arrDataBuffer[i] = (byte)(e.arrDataBuffer[i] + (byte)0x20);
}
}
Console.WriteLine(String.Format("Read {0} response bytes for session {1}", e.iCountOfBytes, e.sessionOwner.id));
}
*/
/// <summary>
/// When the user hits CTRL+C, this event fires. We use this to shut down and unregister our FiddlerCore.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
DoQuit();
}
}
}

You can grant the certificate full trust at:
Settings > General > About > Trust Cert

Related

SignalR in MVC skewing Application Insights

We just started using SignalR in an MVC application and now we're getting a bunch of alerts due to high average response time. I suspect this to be misleading as the application isn't experiencing performance degradation. It appears that SignalR uses this URL to make a connection. This url not a controller/action of the project and just the built in SignalR code in the js file. jquery.signalR-2.2.1.js is the file. I suspect that it is just leaving the websocket connection open while they are on this page and it's skewing our numbers. Is this accurate? If so, is there a way to filter it out of the application insights?
Here is the counter. Is this the expected behavior?
Here is the signalR jquery code where it builds it's url:
// BUG #2953: The url needs to be same otherwise it will cause a memory leak
getUrl: function (connection, transport, reconnecting, poll, ajaxPost) {
/// <summary>Gets the url for making a GET based connect request</summary>
var baseUrl = transport === "webSockets" ? "" : connection.baseUrl,
url = baseUrl + connection.appRelativeUrl,
qs = "transport=" + transport;
if (!ajaxPost && connection.groupsToken) {
qs += "&groupsToken=" + window.encodeURIComponent(connection.groupsToken);
}
if (!reconnecting) {
url += "/connect";
} else {
if (poll) {
// longPolling transport specific
url += "/poll";
} else {
url += "/reconnect";
}
if (!ajaxPost && connection.messageId) {
qs += "&messageId=" + window.encodeURIComponent(connection.messageId);
}
}
url += "?" + qs;
url = transportLogic.prepareQueryString(connection, url);
if (!ajaxPost) {
url += "&tid=" + Math.floor(Math.random() * 11);
}
return url;
},
I fixed this by following the instructions on https://learn.microsoft.com/en-us/azure/application-insights/app-insights-api-filtering-sampling:
Update your ApplicationInsights Nuget package to 2.0.0 or later.
Create a class implementing ITelemetryProcessor:
public class UnwantedTelemetryFilter : ITelemetryProcessor
{
private ITelemetryProcessor Next { get; set; }
public UnwantedTelemetryFilter(ITelemetryProcessor next)
{
this.Next = next;
}
public void Process(ITelemetry item)
{
var request = item as RequestTelemetry;
if (request != null && request.Name != null)
if (request.Name.Contains("signalr"))
return;
// Send everything else:
this.Next.Process(item);
}
}
Add the processor to your Application_Start() in Global.asax.cs:
var builder = TelemetryConfiguration.Active.TelemetryProcessorChainBuilder;
builder.Use((next) => new UnwantedTelemetryFilter(next));
builder.Build();
if the calls are coming from the C# part of the app, the easiest way is to write a custom telemetry processor:
https://learn.microsoft.com/en-us/azure/application-insights/app-insights-api-filtering-sampling
public void Process(ITelemetry item)
{
var request = item as RequestTelemetry;
if (request != null && request.[some field here].Equals("[some signalr specific check here]", StringComparison.OrdinalIgnoreCase))
{
// To filter out an item, just terminate the chain:
return;
}
// Send everything else:
this.Next.Process(item);
}
and use that to explicitly filter out the signalr calls from being sent
or if the calls are coming from JS, then the telemetry initializer there does a similar thing to filter out telemetry if you return false in the initializer.

UCMA Conference call stuck at first participant establishing

I'm trying to start a conference by using UCMA 4.0 basic conferencing sample and first user endpoint stuck at establishing the call (it's lync is not ringing). Timeout exception is triggered after a while. What's the main problem ?
BTW, platform is being started already with discovering 3 application endpoints as established.
sip:kl.dev.local#dev.local;gruu;opaque=srvr:yyapp:EHghH8UXNVqIedXU3YgJyQAAYYApp
sip:kl.cdev.local#dev.local;gruu;opaque=srvr:yyapp:EHghH8UXNVqIedXU3YgJyQAAMachine1
sip:kl.dev.local#dev.local;gruu;opaque=srvr:yyapp:EHghH8UXNVqIedXU3YgJyQAAMachine2
sip:kl.dev.local#dev.local;gruu;opaque=srvr:yyapp:EHghH8UXNVqIedXU3YgJyQAAMachine3
'LyncGame.Gateway.TestConnection.vshost.exe' (CLR v4.0.30319: LyncGame.Gateway.TestConnection.vshost.exe): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Data.SqlXml\v4.0_4.0.0.0__b77a5c561934e089\System.Data.SqlXml.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
'LyncGame.Gateway.TestConnection.vshost.exe' (CLR v4.0.30319: LyncGame.Gateway.TestConnection.vshost.exe): Loaded 'System.Xml.Xsl.CompiledQuery.1'.
The thread 0x6e8 has exited with code 259 (0x103).
The thread 0x3dd4 has exited with code 259 (0x103).
The thread 0x2138 has exited with code 259 (0x103).
'LyncGame.Gateway.TestConnection.vshost.exe' (CLR v4.0.30319: LyncGame.Gateway.TestConnection.vshost.exe): Loaded 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Security\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Security.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
The thread 0x3180 has exited with code 259 (0x103).
The thread 0x350c has exited with code 259 (0x103).
The thread 0x1698 has exited with code 0 (0x0).
The thread 0x3ef4 has exited with code 0 (0x0).
The thread 0x3f00 has exited with code 259 (0x103).
The thread 0x2a24 has exited with code 259 (0x103).
The thread 0x3fc0 has exited with code 259 (0x103).
The thread 0x3750 has exited with code 259 (0x103).
The thread 0x27b8 has exited with code 259 (0x103).
The thread 0x11e0 has exited with code 259 (0x103).
The thread 0x2214 has exited with code 259 (0x103).
The thread 0x1564 has exited with code 259 (0x103).
The thread 0x3740 has exited with code 259 (0x103).
The thread 0x28a8 has exited with code 259 (0x103).
The thread 0x1da8 has exited with code 259 (0x103).
A first chance exception of type 'Microsoft.Rtc.Signaling.OperationTimeoutException' occurred in Microsoft.Rtc.Collaboration.dll
class UCMAConference
{
#region Locals
// The IM to send upon joining the MCU.
private static String _messageToSend = "Hello, World!";
private Conference _conference;
private ApplicationEndpoint _callerEndpoint, _calleeEndpoint;
//Wait handles are only present to keep things synchronous and easy to read.
private AutoResetEvent _waitForCallEstablish = new AutoResetEvent(false);
private AutoResetEvent _waitForConferenceScheduling = new AutoResetEvent(false);
private AutoResetEvent _waitForConferenceJoin = new AutoResetEvent(false);
private AutoResetEvent _waitForMessageReceived = new AutoResetEvent(false);
private AutoResetEvent _waitForMessage2Received = new AutoResetEvent(false);
private AutoResetEvent waitForUserEndpointEstablish = new AutoResetEvent(false);
private AutoResetEvent _waitForShutdown = new AutoResetEvent(false);
private AutoResetEvent _waitForConversationInviteRemoteParticipants = new AutoResetEvent(false);
private InstantMessagingFlow _IMFlow;
private InstantMessagingFlow _IMFlow2;
#endregion
public Conference StartConference()
{
try
{
foreach (var item in PlatformDataProvider.DataProvider.AppEndpoints)
{
WriteLog.AddLine(item.EndpointUri + item.OwnerDisplayName);
Console.WriteLine(item.EndpointUri + item.OwnerDisplayName);
}
// to create end point(s)
UCMACoach ucmaCoach = new UCMACoach();
UserEndpointSettings settings = new UserEndpointSettings("sip:user8#dev.local");
UserEndpoint _userEndpoint = new UserEndpoint(PlatformDataProvider.DataProvider.CollabPlatform, settings);
_userEndpoint.BeginEstablish(ar =>
{
try
{
_userEndpoint.EndEstablish(ar);
waitForUserEndpointEstablish.Set();
}
catch (Exception ex)
{
WriteLog.AddLine("Error on establish: " + ex.Message);
waitForUserEndpointEstablish.Set();
}
}, null);
waitForUserEndpointEstablish.WaitOne();
WriteLog.AddLine("User endpoint has been established ");
_userEndpoint.LocalOwnerPresence.BeginSubscribe(r =>
{
}, null);
//IAsyncResult result = _userEndpoint.LocalOwnerPresence.BeginPublishPresence(Microsoft.Rtc.Collaboration.Presence.PresenceAvailability.Busy);
// Create a user endpoint, using the network credential object
// defined above.
_callerEndpoint = PlatformDataProvider.DataProvider.AppEndpoints[1];
/* friendly name for conference leader endpoint */
// Create a second user endpoint, using the network credential object
// defined above.
_calleeEndpoint = PlatformDataProvider.DataProvider.AppEndpoints[2];
/* friendly name for conference attendee endpoint */
// Get the URI for the user logged onto Microsoft Lync
String _ocUserURI = "sip:user9#dev.local";
// One of the endpoints schedules the conference in advance. At
// schedule time, all the conference settings are set.
// The base conference settings object, used to set the policies for the conference.
ConferenceScheduleInformation conferenceScheduleInformation = new ConferenceScheduleInformation();
// An open meeting (participants can join who are not on the list),
// but requiring authentication (no anonymous users allowed.)
conferenceScheduleInformation.AccessLevel = ConferenceAccessLevel.SameEnterprise;
// The below flag determines whether or not the passcode is optional
// for users joining the conference.
conferenceScheduleInformation.IsPasscodeOptional = true;
conferenceScheduleInformation.Passcode = "1357924680";
// The verbose description of the conference
conferenceScheduleInformation.Description = "StartReady | Conference Testing";
// The below field indicates the date and time after which the conference can be deleted.
conferenceScheduleInformation.ExpiryTime = System.DateTime.Now.AddHours(5);
// These two lines assign a set of modalities (here, only
// InstantMessage) from the available MCUs to the conference. Custom
// modalities (and their corresponding MCUs) may be added at this
// time as part of the extensibility model.
ConferenceMcuInformation instantMessageMCU = new ConferenceMcuInformation(McuType.InstantMessaging);
conferenceScheduleInformation.Mcus.Add(instantMessageMCU);
// Now that the setup object is complete, schedule the conference
// using the conference services off of Endpoint. Note: the conference
// organizer is considered a leader of the conference by default.
_callerEndpoint.ConferenceServices.BeginScheduleConference(conferenceScheduleInformation,
EndScheduleConference, _callerEndpoint.ConferenceServices);
// Wait for the scheduling to complete.
_waitForConferenceScheduling.WaitOne();
// Now that the conference is scheduled, it's time to join it. As we
// already have a reference to the conference object populated from
// the EndScheduleConference call, we do not need to get the
// conference first. Initialize a conversation off of the endpoint,
// and join the conference from the uri provided above.
Conversation callerConversation = new Conversation(_callerEndpoint);
callerConversation.ConferenceSession.StateChanged += new
EventHandler<StateChangedEventArgs<ConferenceSessionState>>(ConferenceSession_StateChanged);
// Join and wait, again forcing synchronization.
callerConversation.ConferenceSession.BeginJoin(_conference.ConferenceUri, null /*joinOptions*/,
EndJoinConference, callerConversation.ConferenceSession);
_waitForConferenceJoin.WaitOne();
// Placing the calls on the conference-connected conversation
// connects to the respective MCUs. These calls may then be used to
// communicate with the conference/MCUs.
InstantMessagingCall instantMessagingCall = new InstantMessagingCall(callerConversation);
// Hooking up event handlers and then placing the call.
instantMessagingCall.InstantMessagingFlowConfigurationRequested +=
this.instantMessagingCall_InstantMessagingFlowConfigurationRequested;
instantMessagingCall.StateChanged += this._call_StateChanged;
instantMessagingCall.BeginEstablish(EndCallEstablish, instantMessagingCall);
//Synchronize to ensure that call has completed.
_waitForCallEstablish.WaitOne();
//send conf invite
ConferenceInvitationDeliverOptions deliverOptions = new ConferenceInvitationDeliverOptions();
deliverOptions.ToastMessage = new ToastMessage("Welcome to conference of StartReady Demo");
ConferenceInvitation invitation = new ConferenceInvitation(callerConversation);
invitation.BeginDeliver(_ocUserURI, deliverOptions, EndDeliverInvitation, invitation);
// Synchronize to ensure that invitation is complete
_waitForConversationInviteRemoteParticipants.WaitOne();
//And from the other endpoint's perspective:
//Initialize a conversation off of the endpoint, and join the
//conference from the uri provided above.
Conversation calleeConversation = new Conversation(_calleeEndpoint);
calleeConversation.ConferenceSession.StateChanged += new
EventHandler<StateChangedEventArgs<ConferenceSessionState>>(ConferenceSession_StateChanged);
// Join and wait, again forcing synchronization.
calleeConversation.ConferenceSession.BeginJoin(_conference.ConferenceUri, null /*joinOptions*/,
EndJoinConference, calleeConversation.ConferenceSession);
_waitForConferenceJoin.WaitOne();
// Placing the calls on the conference-connected conversation
// connects to the respective MCUs. These calls may then be used to
//communicate with the conference/MCUs.
InstantMessagingCall instantMessagingCall2 = new InstantMessagingCall(calleeConversation);
//Hooking up event handlers and then placing the call.
instantMessagingCall2.InstantMessagingFlowConfigurationRequested +=
this.instantMessagingCall2_InstantMessagingFlowConfigurationRequested;
instantMessagingCall2.StateChanged += this._call_StateChanged;
instantMessagingCall2.BeginEstablish(EndCallEstablish, instantMessagingCall2);
//Synchronize to ensure that call has completed.
_waitForCallEstablish.WaitOne();
//Synchronize to ensure that all messages are sent and received
_waitForMessageReceived.WaitOne();
//Wait for shutdown initiated by user
//_waitForShutdown.WaitOne();
//UCMASampleHelper.PauseBeforeContinuing("Press ENTER to shutdown and exit.");
return _conference;
}
catch (Exception ex)
{
WriteLog.AddLine("Cannot start conference: " + ex.Message);
return null;
}
}
#region side methods
void ConferenceSession_StateChanged(object sender, StateChangedEventArgs<ConferenceSessionState> e)
{
ConferenceSession confSession = sender as ConferenceSession;
//Session participants allow for disambiguation.
WriteLog.AddLine("The conference session with Local Participant: " +
confSession.Conversation.LocalParticipant + " has changed state. " +
"The previous conference state was: " + e.PreviousState +
" and the current state is: " + e.State);
}
// Flow created indicates that there is a flow present to begin media
// operations with, and that it is no longer null.
public void instantMessagingCall_InstantMessagingFlowConfigurationRequested
(object sender, InstantMessagingFlowConfigurationRequestedEventArgs e)
{
InstantMessagingFlow instantMessagingFlow = sender as InstantMessagingFlow;
WriteLog.AddLine("Caller's Flow Created.");
instantMessagingFlow = e.Flow;
_IMFlow = instantMessagingFlow;
// Now that the flow is non-null, bind the event handlers for State
// Changed and Message Received. When the flow goes active, (as
// indicated by the state changed event) the program will send the
// IM in the event handler.
instantMessagingFlow.StateChanged += this.instantMessagingFlow_StateChanged;
// Message Received is the event used to indicate that a message has
// been received from the far end.
instantMessagingFlow.MessageReceived += this.instantMessagingFlow_MessageReceived;
}
// Flow created indicates that there is a flow present to begin media
// operations with, and that it is no longer null.
public void instantMessagingCall2_InstantMessagingFlowConfigurationRequested(
object sender, InstantMessagingFlowConfigurationRequestedEventArgs e)
{
InstantMessagingFlow instantMessagingFlow = sender as InstantMessagingFlow;
WriteLog.AddLine("Callee's Flow Created.");
instantMessagingFlow = e.Flow;
_IMFlow2 = instantMessagingFlow;
// Now that the flow is non-null, bind the event handlers for State
// Changed and Message Received. When the flow goes active, the
// program will send the IM in the event handler.
instantMessagingFlow.StateChanged += this.instantMessagingFlow2_StateChanged;
// Message Received is the event used to indicate that a message
// from the far end has been received.
instantMessagingFlow.MessageReceived += this.instantMessagingFlow2_MessageReceived;
}
private void instantMessagingFlow_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
{
InstantMessagingFlow instantMessagingFlow = sender as InstantMessagingFlow;
WriteLog.AddLine("Flow state changed from " + e.PreviousState + " to " + e.State);
//When flow is active, media operations (here, sending an IM) may begin.
if (e.State == MediaFlowState.Active)
{
_IMFlow = instantMessagingFlow;
WriteLog.AddLine("Please type the message to send...");
string msg = Console.ReadLine();
//Send the message on the InstantMessagingFlow.
instantMessagingFlow.BeginSendInstantMessage(msg, EndSendMessage, instantMessagingFlow);
}
}
private void instantMessagingFlow2_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
{
InstantMessagingFlow instantMessagingFlow = sender as InstantMessagingFlow;
WriteLog.AddLine("Flow state changed from " + e.PreviousState + " to " + e.State);
//When flow is active, media operations (here, sending an IM) may begin.
if (e.State == MediaFlowState.Active)
{
_IMFlow2 = instantMessagingFlow;
}
}
private void EndSendMessage(IAsyncResult ar)
{
InstantMessagingFlow instantMessagingFlow = ar.AsyncState as InstantMessagingFlow;
try
{
instantMessagingFlow.EndSendInstantMessage(ar);
WriteLog.AddLine("The message has been sent.");
}
catch (OperationTimeoutException opTimeEx)
{
// OperationFailureException: Indicates failure to connect the
// IM to the remote party due to timeout (called party failed
// to respond within the expected time).
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(opTimeEx.ToString());
}
}
private void instantMessagingFlow_MessageReceived(object sender, InstantMessageReceivedEventArgs e)
{
InstantMessagingFlow instantMessagingFlow = sender as InstantMessagingFlow;
//On an incoming Instant Message, print the contents to the console.
WriteLog.AddLine("In caller's message handler: " + e.Sender.DisplayName + " said: " + e.TextBody);
_waitForMessageReceived.Set();
}
private void instantMessagingFlow2_MessageReceived(object sender, InstantMessageReceivedEventArgs e)
{
InstantMessagingFlow instantMessagingFlow = sender as InstantMessagingFlow;
//On an incoming Instant Message, print the contents to the console.
WriteLog.AddLine("In callee's message handler: " + e.Sender.DisplayName + " said: " + e.TextBody);
WriteLog.AddLine("Message received will be echoed");
_messageToSend = "echo: " + e.TextBody;
//Send the message on the InstantMessagingFlow.
if (_IMFlow2 != null && _IMFlow2.State == MediaFlowState.Active)
{
_IMFlow2.BeginSendInstantMessage(_messageToSend, EndSendMessage, instantMessagingFlow);
}
else
WriteLog.AddLine("Could not echo message because flow was either null or inactive");
_waitForMessage2Received.Set();
}
private void EndCallEstablish(IAsyncResult ar)
{
Call call = ar.AsyncState as Call;
try
{
call.EndEstablish(ar);
WriteLog.AddLine("The call with Local Participant: " + call.Conversation.LocalParticipant +
" and Remote Participant: " + call.RemoteEndpoint.Participant +
" is now in the established state.");
}
catch (OperationFailureException opFailEx)
{
// OperationFailureException: Indicates failure to connect the
// call to the remote party.
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(opFailEx.ToString());
}
catch (RealTimeException exception)
{
// RealTimeException may be thrown on media or link-layer
//failures.
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(exception.ToString());
}
finally
{
//Again, just to sync the completion of the code.
_waitForCallEstablish.Set();
}
}
private void EndDeliverInvitation(IAsyncResult ar)
{
ConferenceInvitation invitation = ar.AsyncState as ConferenceInvitation;
try
{
invitation.EndDeliver(ar);
}
catch (OperationFailureException opFailEx)
{
// OperationFailureException: Indicates failure to connect the
// call to the remote party.
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(opFailEx.ToString());
}
catch (RealTimeException exception)
{
// RealTimeException may be thrown on media or link-layer failures.
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(exception.ToString());
}
finally
{
//Again, just to sync the completion of the code.
_waitForConversationInviteRemoteParticipants.Set();
}
}
private void EndScheduleConference(IAsyncResult ar)
{
ConferenceServices confSession = ar.AsyncState as ConferenceServices;
try
{
//End schedule conference returns the conference object, which
// contains the vast majority of the data relevant to that
// conference.
_conference = confSession.EndScheduleConference(ar);
WriteLog.AddLine("");
WriteLog.AddLine(" The conference is now scheduled.");
WriteLog.AddLine("");
}
catch (ConferenceFailureException confFailEx)
{
// ConferenceFailureException may be thrown on failures to
// schedule due to MCUs being absent or unsupported, or due to
// malformed parameters.
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(confFailEx.ToString());
}
//Again, for sync. reasons.
_waitForConferenceScheduling.Set();
}
private void EndJoinConference(IAsyncResult ar)
{
ConferenceSession confSession = ar.AsyncState as ConferenceSession;
try
{
confSession.EndJoin(ar);
}
catch (ConferenceFailureException confFailEx)
{
// ConferenceFailureException may be thrown on failures due to
// MCUs being absent or unsupported, or due to malformed parameters.
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(confFailEx.ToString());
}
catch (RealTimeException rTEx)
{
// TODO (Left to the reader): Add error handling code
WriteLog.AddLine(rTEx.ToString());
}
finally
{
//Again, for sync. reasons.
_waitForConferenceJoin.Set();
}
}
//Just to record the state transitions in the console.
void _call_StateChanged(object sender, CallStateChangedEventArgs e)
{
Call call = sender as Call;
//Call participants allow for disambiguation.
WriteLog.AddLine("The call with Local Participant: " + call.Conversation.LocalParticipant +
" has changed state. The previous call state was: " + e.PreviousState +
" and the current state is: " + e.State);
}
#endregion
}
}
The other thing that's really useful here is to run OCSLogger on the application server and see what comes back. You can run S4 to get SIP traces. Might just be something simple like you're not able to receive messages on 5061 (or whichever callback port you specified in your provisioning). If it's only failing on A/V, look at what addresses are being used in the SDP offer/answer and see if those are reachable as well. Again, OCSLogger (or the Lync CLS) and Snooper are your friends here.

FlumeRpcClient multithreading

I'm trying to understand the correct way to use the Flume RpcClient in a multithreaded application. Information I have found so far indicates that the components are thread safe, but the example in the Flume documentation clouds the issue when it comes to error handling. This code:
public void sendDataToFlume(String data) {
// Create a Flume Event object that encapsulates the sample data
Event event = EventBuilder.withBody(data, Charset.forName("UTF-8"));
// Send the event
try {
client.append(event);
} catch (EventDeliveryException e) {
// clean up and recreate the client
client.close();
client = null;
client = RpcClientFactory.getDefaultInstance(hostname, port);
// Use the following method to create a thrift client (instead of the above line):
// this.client = RpcClientFactory.getThriftInstance(hostname, port);
}
}
If more then one thread calls this method, and the exception is thrown, then there will be a problem as multiple threads try and recreate the client in the exception handler.
Is the intent of the SDK that it should only be used by a single thread? Should this method be synchronized, as it appears to be in the log4jappender that is part of the Flume source? Should I put this code in its own worker and pass it events via a queue?
Does anyone have an example of RpcClient being used by more then one thread (included the error condition)?
Would I be better off using the "embedded agent"? Is that multithread friendly?
With the embedded agent, you get the same case except you don't know what to do:
try {
agent.put(event);
} catch (EventDeliveryException e) {
// ???
}
You could stop the agent, and restart it - but you would need a synchronized block (or a ReentrantReadWriteLock, to not block thread while "reading" the client field). But since I'm not a Flume expert, I can't tell you which one is better.
Example:
class MyClass {
private final ReentrantReadWriteLocklock;
private final Lock readLock;
private final Lock writeLock;
private RpcClient client;
private final String hostname;
private final Integer port;
// Constructor
MyClass(String hostname, Integer port) {
this.hostname = Objects.requireNonNull(hostname, "hostname");
this.port = Objects.requireNonNull(port, "port");
this.lock = new ReentrantReadWriteLock();
this.readLock = this.lock.readLock();
this.writeLock = this.lock.writeLock();
this.client = buildClient();
}
private RpcClient buildClient() {
return RpcClientFactory.getDefaultInstance(hostname, port);
}
public void sendDataToFlume(String data) {
// Create a Flume Event object that encapsulates the sample data
Event event = EventBuilder.withBody(data, Charset.forName("UTF-8"));
// Send the event
readLock.lock(); // lock for reading 'client'
try {
try {
client.append(event);
} catch (EventDeliveryException e) {
writeLock.lock(); // lock for reading/writing client
try {
// clean up and recreate the client
client.close();
client = null;
client = buildClient();
} finally {
writeLock.unlock();
}
}
} finally {
readLock.unlock();
}
}
}
Beside, the example will lose the event because it is not sent back. Some kind of loop + a max retry would probably do the trick:
int i = 0;
for (; i < maxRetry; ++i) {
try {
client.append(event);
break;
} catch (EventDeliveryException e) {
// clean up and recreate the client
client.close();
client = null;
client = RpcClientFactory.getDefaultInstance(hostname, port);
// Use the following method to create a thrift client (instead of the above line):
// this.client = RpcClientFactory.getThriftInstance(hostname, port);
}
}
if (i == maxRetry) {
logger.error("flume client is offline, loosing events {}", event);
}
That's the idea, but I don't think that should be the task of the user (eg: us), but an option in the client or the agent to store event that could not be processed due to such errors.

commons.net FTPSClient.storeFile doesn't throw IOException if connection with server is lost

Background:
I'm attempting to add some level fault tolerance to an application that uses Apache Commons.net FTPSClient to transfer files. If the connection between the client and server fails, I'd like to capture the produced exception/return code, log the details, and attempt to reconnect/retry the transfer.
What works:
The retrieveFile() method. If the connection fails, (i.e. I disable the server's public interface), I receive a CopyStreamException caused by a SocketTimeoutException after the amount of time I specified as the timeout.
What doesn't work:
The storeFile() method. If I initiate a transfer via storeFile() and disable the server's public interface, the storeFile() method blocks/hangs indefinitely with out throwing any exceptions.
Here is a simple app that hangs if the connection is terminated:
public class SmallTest {
private static Logger log = Logger.getLogger(SmallTest.class);
/**
* #param args
* #throws IOException
*/
public static void main(String[] args) throws IOException {
FTPSClient client = new FTPSClient(true);
FTPSCredentials creds = new FTPSCredentials("host", "usr", "pass",
"/keystore/ftpclient.jks", "pass",
"/keystore/rootca.jks");
String file = "/file/jdk-7u21-linux-x64.rpm";
String destinationFile = "/jdk-7u21-linux-x64.rpm";
client.setTrustManager(TrustManagerUtils.getValidateServerCertificateTrustManager());
client.setKeyManager(creds.getKeystoreManager());
client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out), true));
client.setCopyStreamListener(createListener());
client.setConnectTimeout(5000);
client.setDefaultTimeout(5000);
client.connect(creds.getHost(), 990);
client.setSoTimeout(5000);
client.setDataTimeout(5000);
if (!FTPReply.isPositiveCompletion(client.getReplyCode())) {
client.disconnect();
log.error("ERROR: " + creds.getHost() + " refused the connection");
} else {
if (client.login(creds.getUser(), creds.getPass())) {
log.debug("Logged in as " + creds.getUser());
client.enterLocalPassiveMode();
client.setFileTransferMode(FTP.BLOCK_TRANSFER_MODE);
client.setFileType(FTP.BINARY_FILE_TYPE);
InputStream inputStream = new FileInputStream(file);
log.debug("Invoking storeFile()");
if (!client.storeFile(destinationFile, inputStream)) {
log.error("ERROR: Failed to store " + file
+ " on remote host. Last reply code: "
+ client.getReplyCode());
} else {
log.debug("Stored the file...");
}
inputStream.close();
client.logout();
client.disconnect();
} else {
log.error("Could not log into " + creds.getHost());
}
}
}
private static CopyStreamListener createListener(){
return new CopyStreamListener(){
private long megsTotal = 0;
#Override
public void bytesTransferred(CopyStreamEvent event) {
bytesTransferred(event.getTotalBytesTransferred(), event.getBytesTransferred(), event.getStreamSize());
}
#Override
public void bytesTransferred(long totalBytesTransferred,
int bytesTransferred, long streamSize) {
long megs = totalBytesTransferred / 1000000;
for (long l = megsTotal; l < megs; l++) {
System.out.print("#");
}
megsTotal = megs;
}
};
}
Is there any way to make the connection ACTUALLY timeout?
SW Versions:
Commons.net v3.3
Java 7
CentOS 6.3
Thanks in advance,
Joe
I ran into this same problem, and I think that I was able to get something that seems to work with the desired timeout behavior when I unplug the ethernet cable on my laptop.
I use 'storeFileStream' instead of 'storeFile', and then use 'completePendingCommand' to finish the transfer. You can check the Apache commons docs for 'completePendingCommand' to see an example of this kind of transfer. It took about 15 mins for it to timeout for me. One other thing: the aforementioned docs include calling 'isPositiveIntermediate' to check for an error, but this wasn't working. I replaced it with 'isPositivePreliminary' and now it seems to work. I'm not sure if that's actually correct, but it's the best I've found so far.

Print barcodes from web page to Zebra printer

We're trying to print barcodes from a web page to our Zebra printer.
I'm wondering if there's a way to print them using the printer's own font perhaps using web fonts or if I knew the font name used?
I have been trying to use php barcode generators, that basically generates images containing the barcode. I have in fact been trying this approach for a few days already, without success.
The problem is when I print them it's not readable by the scanners. I have tried to change the image resolution to match that of the printer (203dpi), also tried playing with the image size and formats, but the barcodes after printed still can't be scanned.
So does anybody have experience with this?
Printer: Zebra TLP 2844
Barcodes required per page:
01 Code39 horizontal (scanable only if printed at very specific size and browser)
01 Code128 vertical (still can't get it to work, print is always very blurry and won't get scanned)
===========
I've made a little bit of progress, I found out this printer supports EPL2 language, so I'm trying to use it to print out the barcodes.
First I needed to enable pass through mode, I did that on Printer Options > Advanced Setup > Miscellaneous.
Now I'm able to print barcodes impeccably using the printer's built-in font :D using this command:
ZPL:
B10,10,0,1,2,2,60,N,"TEXT-GOES-HERE"
:ZPL
But I can only print it from Notepad, I'm still unable to print this from a browser... It's probably a problem with LF being replaced with CR+LF...
How to overcome this problem??
===========
The label I'm trying to print actually has a bit of text before the barcode, with some html tables formatting it nicely. So I need to print this first, and in the middle I need to stick in a nice label and then add some more text.
So I can't use pure EPL2 to print the whole thing, I'm wondering if I can use some of both html + EPL2 + html to achieve my goal or is that not allowed?? =/
You are running into a few obstacles:
1) When you print through the OS installed printer driver, the printer driver is trying to take the data that is sent to it and (re)rasterize or scale it for the output device (the Zebra printer). Since the printer is a relatively low resolution at 203dpi, then it does not take too much for the scaling the print driver is having to do for it to loose some integrity in the quality of the barcode. This is why barcodes generated using the direct ZPL commands are much more reliable.
2) Due to the security that web browsers purposefully provide by not allowing access to the client computer, you cannot directly communicate with the client connected printer. This sandboxing is what helps to protect users from malware so that nefarious websites cannot do things like write files to the client machine or send output directly to devices such as printers. So you are not able to directly send the ZPL commands through the browser to the client connected printer.
However, there is a way to do what you describe. The steps necessary are typically only going to be useful if you have some degree of control over the client computer accessing the site that is trying to print to the Zebra printers. For example this is only going to be used by machines on your company network, or by clients who are willing to install a small application that you need to write. To do this, you will need to look at the following steps:
A) You need to make up your own custom MIME type. This is basically just any name you want to use that is not going to collide with any registered MIME types.
B) Next you will define a filename extension that will map to your custom MIME type. To do this, you typically will need to configure your web server (steps for this depend on what web server you are using) to allow the new MIME type you want to define and what file extension is used for these types of files.
C) Then on your web application, when you want to output the ZPL data, you write it to a file using a filename extension that is mapped to your new MIME type. Then once the file is generated, you can either provide an HTML link to it, or redirect the client browser to the file. You can test if your file is working correctly at this point by manually copying the file you created directly to the raw printer port.
D) Next you need to write a small application which can be installed on the client. When the application is installed, you need to have it register itself as a valid consuming application for your custom MIME type. If a browser detects that there is an installed application for a file of the specified MIME type, it simply writes the file to a temporary directory on the client machine and then attempts to launch the application of the same registered MIME type with the temporary file as a parameter to the application. Thus your application now just reads the file that the browser passed to it and then it attempts to dump it directly to the printer.
This is an overview of what you need to do in order to accomplish what you are describing. Some of the specific steps will depend on what type of web server you are using and what OS your clients machines are. But this is the high level overview that will let you accomplish what you are attempting.
If you'd consider loading a java applet, qz-print (previously jzebra) can do exactly what you are describing and works nicely with the LP2844 mentioned in the comments.
https://code.google.com/p/jzebra/
What we did for our web app :
1) Download the free printfile app http://www.lerup.com/printfile/
"PrintFile is a freeware MS Windows utility program that will enable you to print files fast and easily. The program recognizes plain text, PostScript, Encapsulated PostScript (EPS) and binary formats. Using this program can save you a lot of paper and thereby also saving valuable natural resources."
When you first run PrintFile, go into the advanced options and enable "send to printer directly".
2) Setup the ZEBRA printer in windows as a Generic Text Printer.
2) Generate a file.prt file in the web app which is just a plain text EPL file.
3) Double clicking on the downloaded file will instantly print the barcode. Works like a charm. You can even setup PrintFile so that you don't even see a gui.
I am using QZ Tray to print labels from a web page to Zebra thermal printer.
In the demo/js folder of QZ Tray there are three JavaScript files that are required to communicate with QZ Tray application - dependencies/rsvp-3.1.0.min.js, dependencies/sha-256.min.js and qz-tray.js.
Include these JavaScript files in your project as follows:
<script type="text/javascript" src="/lib/qz-tray/rsvp-3.1.0.min.js"></script>
<script type="text/javascript" src="/lib/qz-tray/sha-256.min.js"></script>
<script type="text/javascript" src="/lib/qz-tray/qz-tray.js"></script>
The most simple way to print a label to Zebra thermal printer is shown below.
<script type="text/javascript">
qz.websocket.connect().then(function() {
// Pass the printer name into the next Promise
return qz.printers.find("zebra");
}).then(function(printer) {
// Create a default config for the found printer
var config = qz.configs.create(printer);
// Raw ZPL
var data = ['^XA^FO50,50^ADN,36,20^FDRAW ZPL EXAMPLE^FS^XZ'];
return qz.print(config, data);
}).catch(function(e) { console.error(e); });
</script>
See How to print labels from a web page to Zebra thermal printer for more information.
You can also send the ZPL commands in a text file (you can pack multiple labels in a single file) and have the user open and print the file via windows notepad. The only caveat is that they have to remove the default header and footer (File --> Page Setup).
Its a bit of user training, but may be acceptable if you don't have control over the client machines.
I'm developing something similar here.
I need to print in a LP2844 from my webapp. The problem is that my webapp is in a remote server in the cloud (Amazon EC2) and the printer is going to be in a warehouse desk.
My solution:
The webapp generates the EPL2 code for the label with the barcodes, then publish a PubNub message.
I wrote a little C# program that runs in the computer where the printer is connected. The program receives the message and then send the code to the printer.
I followed the idea proposed by "Tres Finocchiaro" on my application based on:
ASP.NET 4.0
IIS
Chrome, IExplorer, Firefox
Zebra TLP 2844
EPL protocolo
Unfortunatly the jzebra needs some improvements to work corectly due to the issues of security of current browser.
Installing jzebra
Downlod jzebdra and from dist directory I copy into your directory (eg. mydir):
web
mydir
js
..
deployJava.js
lib
..
qz-print.jar
qz-print_jnlp.jnlp
Create your print.html
<html>
<script type="text/javascript" src="js/deployJava.js"></script>
<script type="text/javascript">
/**
* Optionally used to deploy multiple versions of the applet for mixed
* environments. Oracle uses document.write(), which puts the applet at the
* top of the page, bumping all HTML content down.
*/
deployQZ();
/** NEW FUNCTION **/
function initPrinter() {
findPrinters();
useDefaultPrinter();
}
/** NEW FUNCTION **/
function myalert(txt) {
alert(txt);
}
/**
* Deploys different versions of the applet depending on Java version.
* Useful for removing warning dialogs for Java 6. This function is optional
* however, if used, should replace the <applet> method. Needed to address
* MANIFEST.MF TrustedLibrary=true discrepency between JRE6 and JRE7.
*/
function deployQZ() {
var attributes = {id: "qz", code:'qz.PrintApplet.class',
archive:'qz-print.jar', width:1, height:1};
var parameters = {jnlp_href: 'qz-print_jnlp.jnlp',
cache_option:'plugin', disable_logging:'false',
initial_focus:'false'};
if (deployJava.versionCheck("1.7+") == true) {}
else if (deployJava.versionCheck("1.6+") == true) {
delete parameters['jnlp_href'];
}
deployJava.runApplet(attributes, parameters, '1.5');
}
/**
* Automatically gets called when applet has loaded.
*/
function qzReady() {
// Setup our global qz object
window["qz"] = document.getElementById('qz');
var title = document.getElementById("title");
if (qz) {
try {
title.innerHTML = title.innerHTML + " " + qz.getVersion();
document.getElementById("content").style.background = "#F0F0F0";
} catch(err) { // LiveConnect error, display a detailed meesage
document.getElementById("content").style.background = "#F5A9A9";
alert("ERROR: \nThe applet did not load correctly. Communication to the " +
"applet has failed, likely caused by Java Security Settings. \n\n" +
"CAUSE: \nJava 7 update 25 and higher block LiveConnect calls " +
"once Oracle has marked that version as outdated, which " +
"is likely the cause. \n\nSOLUTION: \n 1. Update Java to the latest " +
"Java version \n (or)\n 2. Lower the security " +
"settings from the Java Control Panel.");
}
}
}
/**
* Returns whether or not the applet is not ready to print.
* Displays an alert if not ready.
*/
function notReady() {
// If applet is not loaded, display an error
if (!isLoaded()) {
return true;
}
// If a printer hasn't been selected, display a message.
else if (!qz.getPrinter()) {
/** CALL TO NEW FUNCTION **/
initPrinter();
return false;
}
return false;
}
/**
* Returns is the applet is not loaded properly
*/
function isLoaded() {
if (!qz) {
alert('Error:\n\n\tPrint plugin is NOT loaded!');
return false;
} else {
try {
if (!qz.isActive()) {
alert('Error:\n\n\tPrint plugin is loaded but NOT active!');
return false;
}
} catch (err) {
alert('Error:\n\n\tPrint plugin is NOT loaded properly!');
return false;
}
}
return true;
}
/**
* Automatically gets called when "qz.print()" is finished.
*/
function qzDonePrinting() {
// Alert error, if any
if (qz.getException()) {
alert('Error printing:\n\n\t' + qz.getException().getLocalizedMessage());
qz.clearException();
return;
}
// Alert success message
alert('Successfully sent print data to "' + qz.getPrinter() + '" queue.');
}
/***************************************************************************
* Prototype function for finding the "default printer" on the system
* Usage:
* qz.findPrinter();
* window['qzDoneFinding'] = function() { alert(qz.getPrinter()); };
***************************************************************************/
function useDefaultPrinter() {
if (isLoaded()) {
// Searches for default printer
qz.findPrinter();
// Automatically gets called when "qz.findPrinter()" is finished.
window['qzDoneFinding'] = function() {
// Alert the printer name to user
var printer = qz.getPrinter();
myalert(printer !== null ? 'Default printer found: "' + printer + '"':
'Default printer ' + 'not found');
// Remove reference to this function
window['qzDoneFinding'] = null;
};
}
}
/***************************************************************************
* Prototype function for finding the closest match to a printer name.
* Usage:
* qz.findPrinter('zebra');
* window['qzDoneFinding'] = function() { alert(qz.getPrinter()); };
***************************************************************************/
function findPrinter(name) {
// Get printer name from input box
var p = document.getElementById('printer');
if (name) {
p.value = name;
}
if (isLoaded()) {
// Searches for locally installed printer with specified name
qz.findPrinter(p.value);
// Automatically gets called when "qz.findPrinter()" is finished.
window['qzDoneFinding'] = function() {
var p = document.getElementById('printer');
var printer = qz.getPrinter();
// Alert the printer name to user
alert(printer !== null ? 'Printer found: "' + printer +
'" after searching for "' + p.value + '"' : 'Printer "' +
p.value + '" not found.');
// Remove reference to this function
window['qzDoneFinding'] = null;
};
}
}
/***************************************************************************
* Prototype function for listing all printers attached to the system
* Usage:
* qz.findPrinter('\\{dummy_text\\}');
* window['qzDoneFinding'] = function() { alert(qz.getPrinters()); };
***************************************************************************/
function findPrinters() {
if (isLoaded()) {
// Searches for a locally installed printer with a bogus name
qz.findPrinter('\\{bogus_printer\\}');
// Automatically gets called when "qz.findPrinter()" is finished.
window['qzDoneFinding'] = function() {
// Get the CSV listing of attached printers
var printers = qz.getPrinters().split(',');
for (i in printers) {
myalert(printers[i] ? printers[i] : 'Unknown');
}
// Remove reference to this function
window['qzDoneFinding'] = null;
};
}
}
/***************************************************************************
* Prototype function for printing raw EPL commands
* Usage:
* qz.append('\nN\nA50,50,0,5,1,1,N,"Hello World!"\n');
* qz.print();
***************************************************************************/
function print() {
if (notReady()) { return; }
// Send characters/raw commands to qz using "append"
// This example is for EPL. Please adapt to your printer language
// Hint: Carriage Return = \r, New Line = \n, Escape Double Quotes= \"
qz.append('\nN\n');
qz.append('q609\n');
qz.append('Q203,26\n');
qz.append('B5,26,0,1A,3,7,152,B,"1234"\n');
qz.append('A310,26,0,3,1,1,N,"SKU 00000 MFG 0000"\n');
qz.append('A310,56,0,3,1,1,N,"QZ PRINT APPLET"\n');
qz.append('A310,86,0,3,1,1,N,"TEST PRINT SUCCESSFUL"\n');
qz.append('A310,116,0,3,1,1,N,"FROM SAMPLE.HTML"\n');
qz.append('A310,146,0,3,1,1,N,"QZINDUSTRIES.COM"');
// Append the rest of our commands
qz.append('\nP1,1\n');
// Tell the applet to print.
qz.print();
}
/***************************************************************************
* Prototype function for logging a PostScript printer's capabilites to the
* java console to expose potentially new applet features/enhancements.
* Warning, this has been known to trigger some PC firewalls
* when it scans ports for certain printer capabilities.
* Usage: (identical to appendImage(), but uses html2canvas for png rendering)
* qz.setLogPostScriptFeatures(true);
* qz.appendHTML("<h1>Hello world!</h1>");
* qz.printPS();
***************************************************************************/
function logFeatures() {
if (isLoaded()) {
var logging = qz.getLogPostScriptFeatures();
qz.setLogPostScriptFeatures(!logging);
alert('Logging of PostScript printer capabilities to console set to "' + !logging + '"');
}
}
/***************************************************************************
****************************************************************************
* * HELPER FUNCTIONS **
****************************************************************************
***************************************************************************/
function getPath() {
var path = window.location.href;
return path.substring(0, path.lastIndexOf("/")) + "/";
}
/**
* Fixes some html formatting for printing. Only use on text, not on tags!
* Very important!
* 1. HTML ignores white spaces, this fixes that
* 2. The right quotation mark breaks PostScript print formatting
* 3. The hyphen/dash autoflows and breaks formatting
*/
function fixHTML(html) {
return html.replace(/ /g, " ").replace(/’/g, "'").replace(/-/g,"‑");
}
/**
* Equivelant of VisualBasic CHR() function
*/
function chr(i) {
return String.fromCharCode(i);
}
/***************************************************************************
* Prototype function for allowing the applet to run multiple instances.
* IE and Firefox may benefit from this setting if using heavy AJAX to
* rewrite the page. Use with care;
* Usage:
* qz.allowMultipleInstances(true);
***************************************************************************/
function allowMultiple() {
if (isLoaded()) {
var multiple = qz.getAllowMultipleInstances();
qz.allowMultipleInstances(!multiple);
alert('Allowing of multiple applet instances set to "' + !multiple + '"');
}
}
</script>
<input type="button" onClick="print()" />
</body>
</html>
the code provided is based on "jzebra_installation/dist/sample.html".
try creating a websocket that controls the print on the client side and send data with ajax from the page to localhost.
/// websocket
using System;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
namespace Server
{
class Program
{
public static WebsocketServer ws;
static void Main(string[] args)
{
ws = new Server.WebsocketServer();
ws.LogMessage += Ws_LogMessage;
ws.Start("http://localhost:2645/service/");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static void Ws_LogMessage(object sender, WebsocketServer.LogMessageEventArgs e)
{
Console.WriteLine(e.Message);
}
}
public class WebsocketServer
{
public event OnLogMessage LogMessage;
public delegate void OnLogMessage(Object sender, LogMessageEventArgs e);
public class LogMessageEventArgs : EventArgs
{
public string Message { get; set; }
public LogMessageEventArgs(string Message)
{
this.Message = Message;
}
}
public bool started = false;
public async void Start(string httpListenerPrefix)
{
HttpListener httpListener = new HttpListener();
httpListener.Prefixes.Add(httpListenerPrefix);
httpListener.Start();
LogMessage(this, new LogMessageEventArgs("Listening..."));
started = true;
while (started)
{
HttpListenerContext httpListenerContext = await httpListener.GetContextAsync();
if (httpListenerContext.Request.IsWebSocketRequest)
{
ProcessRequest(httpListenerContext);
}
else
{
httpListenerContext.Response.StatusCode = 400;
httpListenerContext.Response.Close();
LogMessage(this, new LogMessageEventArgs("Closed..."));
}
}
}
public void Stop()
{
started = false;
}
private async void ProcessRequest(HttpListenerContext httpListenerContext)
{
WebSocketContext webSocketContext = null;
try
{
webSocketContext = await httpListenerContext.AcceptWebSocketAsync(subProtocol: null);
LogMessage(this, new LogMessageEventArgs("Connected"));
}
catch (Exception e)
{
httpListenerContext.Response.StatusCode = 500;
httpListenerContext.Response.Close();
LogMessage(this, new LogMessageEventArgs(String.Format("Exception: {0}", e)));
return;
}
WebSocket webSocket = webSocketContext.WebSocket;
try
{
while (webSocket.State == WebSocketState.Open)
{
ArraySegment<Byte> buffer = new ArraySegment<byte>(new Byte[8192]);
WebSocketReceiveResult result = null;
using (var ms = new System.IO.MemoryStream())
{
do
{
result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
ms.Write(buffer.Array, buffer.Offset, result.Count);
}
while (!result.EndOfMessage);
ms.Seek(0, System.IO.SeekOrigin.Begin);
if (result.MessageType == WebSocketMessageType.Text)
{
using (var reader = new System.IO.StreamReader(ms, Encoding.UTF8))
{
var r = System.Text.Encoding.UTF8.GetString(ms.ToArray());
var t = Newtonsoft.Json.JsonConvert.DeserializeObject<Datos>(r);
bool valid = true;
byte[] toBytes = Encoding.UTF8.GetBytes(""); ;
if (t != null)
{
if (t.printer.Trim() == string.Empty)
{
var printers = "";
foreach (var imp in System.Drawing.Printing.PrinterSettings.InstalledPrinters)
{
printers += imp + "\n";
}
toBytes = Encoding.UTF8.GetBytes("No se Indicó la Impresora\nLas Impresoras disponibles son: " + printers);
valid = false;
}
if (t.name.Trim() == string.Empty)
{
toBytes = Encoding.UTF8.GetBytes("No se Indicó el nombre del Documento");
valid = false;
}
if (t.code == null)
{
toBytes = Encoding.UTF8.GetBytes("No hay datos para enviar a la Impresora");
valid = false;
}
if (valid)
{
print.RawPrinter.SendStringToPrinter(t.printer, t.code, t.name);
toBytes = Encoding.UTF8.GetBytes("Correcto...");
}
await webSocket.SendAsync(new ArraySegment<byte>(toBytes, 0, int.Parse(toBytes.Length.ToString())), WebSocketMessageType.Binary, result.EndOfMessage, CancellationToken.None);
}
else
{
toBytes = Encoding.UTF8.GetBytes("Error...");
await webSocket.SendAsync(new ArraySegment<byte>(toBytes, 0, int.Parse(toBytes.Length.ToString())), WebSocketMessageType.Binary, result.EndOfMessage, CancellationToken.None);
}
}
}
}
}
}
catch (Exception e)
{
LogMessage(this, new LogMessageEventArgs(String.Format("Exception: {0} \nLinea:{1}", e, e.StackTrace)));
}
finally
{
if (webSocket != null)
webSocket.Dispose();
}
}
}
public class Datos
{
public string name { get; set; }
public string code { get; set; }
public string printer { get; set; } = "";
}
}
raw Print:
using Microsoft.VisualBasic;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
namespace print
{
public class RawPrinter
{
// Structure and API declarions:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public class DOCINFOA
{
[MarshalAs(UnmanagedType.LPStr)]
public string pDocName;
[MarshalAs(UnmanagedType.LPStr)]
public string pOutputFile;
[MarshalAs(UnmanagedType.LPStr)]
public string pDataType;
}
[DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)]
string szPrinter, ref IntPtr hPriknter, IntPtr pd);
[DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool ClosePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In(), MarshalAs(UnmanagedType.LPStruct)]
DOCINFOA di);
[DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndDocPrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool StartPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool EndPagePrinter(IntPtr hPrinter);
[DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, ref Int32 dwWritten);
// SendBytesToPrinter()
// When the function is given a printer name and an unmanaged array
// of bytes, the function sends those bytes to the print queue.
// Returns true on success, false on failure.
public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount, string DocName = "")
{
Int32 dwError = 0;
Int32 dwWritten = 0;
IntPtr hPrinter = new IntPtr(0);
DOCINFOA di = new DOCINFOA();
bool bSuccess = false;
// Assume failure unless you specifically succeed.
di.pDocName = string.IsNullOrEmpty(DocName) ? "My C#.NET RAW Document" : DocName;
di.pDataType = "RAW";
// Open the printer.
if (OpenPrinter(szPrinterName.Normalize(), ref hPrinter, IntPtr.Zero))
{
// Start a document.
if (StartDocPrinter(hPrinter, 1, di))
{
// Start a page.
if (StartPagePrinter(hPrinter))
{
// Write your bytes.
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, ref dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
// If you did not succeed, GetLastError may give more information
// about why not.
if (bSuccess == false)
{
dwError = Marshal.GetLastWin32Error();
}
return bSuccess;
}
public static bool SendFileToPrinter(string szPrinterName, string szFileName)
{
// Open the file.
FileStream fs = new FileStream(szFileName, FileMode.Open);
// Create a BinaryReader on the file.
BinaryReader br = new BinaryReader(fs);
// Dim an array of bytes big enough to hold the file's contents.
Byte[] bytes = new Byte[fs.Length];
bool bSuccess = false;
// Your unmanaged pointer.
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength = 0;
nLength = Convert.ToInt32(fs.Length);
// Read the contents of the file into the array.
bytes = br.ReadBytes(nLength);
// Allocate some unmanaged memory for those bytes.
pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
// Copy the managed byte array into the unmanaged array.
Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
// Send the unmanaged bytes to the printer.
bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
// Free the unmanaged memory that you allocated earlier.
Marshal.FreeCoTaskMem(pUnmanagedBytes);
return bSuccess;
}
public static bool SendStringToPrinter(string szPrinterName, string szString, string DocName = "")
{
IntPtr pBytes = default(IntPtr);
Int32 dwCount = default(Int32);
// How many characters are in the string?
dwCount = szString.Length;
// Assume that the printer is expecting ANSI text, and then convert
// the string to ANSI text.
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
// Send the converted ANSI string to the printer.
SendBytesToPrinter(szPrinterName, pBytes, dwCount, DocName);
Marshal.FreeCoTaskMem(pBytes);
return true;
}
}
}
html page:
<!DOCTYPE html>
<html>
<head>
</head>
<body ng-app="myapp">
<div ng-controller="try as ctl">
<input ng-model="ctl.ticket.nombre">
<textarea ng-model="ctl.ticket.code"></textarea>
<button ng-click="ctl.send()">Enviar</button>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var ws = new WebSocket("ws://localhost:2645/service");
ws.binaryType = "arraybuffer";
ws.onopen = function () {
console.log('connection is opened!!!');
};
ws.onmessage = function (evt) {
console.log(arrayBufferToString(evt.data))
};
ws.onclose = function () {
console.log("Connection is Closed...")
};
function arrayBufferToString(buffer) {
var arr = new Uint8Array(buffer);
var str = String.fromCharCode.apply(String, arr);
return decodeURIComponent(escape(str));
}
var app = angular.module('myapp', []);
app.controller('try', function () {
this.ticket= {nombre:'', estado:''}
this.send = () => {
var toSend= JSON.stringify(this.ticket);
ws.send(toSend);
}
});
</script>
</body>
</html>
then send a ZPL code from html(write this on textarea code);
^XA
^FO200,50^BY2^B3N,N,80,Y,N^FD0123456789^FS
^PQ1^XZ

Resources