The Rest.li client framework provides support for accessing resources defined using Rest.li. The client framework consists of two parts:
RequestBuilder
classes, which provide an interface for
creating REST requests to access a specific method of a resource.
Request builders work entirely in-memory and do not communicate with
remote endpoints.RestClient
, which provides an interface for sending
requests to remote endpoints and receiving responses.The request builder portion of the framework can be further divided into two layers:
Most developers should work with the type-safe request builders, unless there is a specific need to work with arbitrary resources whose interfaces are unknown at the time the code is written.
Usually, developers building Rest.li services publish Java client bindings for the Rest.li resources their service provides as artifacts into a shared repository, such as a maven repo. By adding a dependency to these artifacts, other developers can quickly get their hands on the request builder classes defined in these client bindings to make requests to the resources provided by that service.
To add a dependency from a gradle project, add the artifact containing
the rest client bindings to your dependency list. If you are unsure of
the name of the artifact, ask the service owners. They are usually the
artifact with a name ending in -client, -api or -rest. Note that the
configuration
for the dependency must be set to restClient
:
build.gradle:
...
dependencies {
// for a local project:
compile project(path: ':example-api', configuration: 'restClient')
// for a versioned artifact:
compile group: 'org.somegroup', name: 'example-api', version: '1.0',
configuration: 'restClient'\
}
...
To add a dependency to Java bindings for data models, add a
dataTemplate
configured dependency in your build.gradle,
for example:
build.gradle:
...
dependencies {
// for a local project:
compile project(path: ':example-api', configuration: 'dataTemplate')
// for a versioned artifact:
compile group: 'org.somegroup', name: 'example-api', version: '1.0',
configuration: 'dataTemplate'\
}
...
Note that you should not usually need to add such a dependency when
adding a restClient
dependency, as the
restClient
should bring in the dataTemplate
transitively.
Note: If you are writing pegasus schemas (.pdsc files) and need to add a
dependency on other pegasus schemas, you need to add a
dataModel
dependency:
build.gradle
...
dataModel spec.product.example.data
...
The client framework includes a code-generation tool that reads the IDL and generates type-safe Java binding for each resource and its supported methods. The bindings are represented as RequestBuilder classes.
For each resource described in an IDL file, a corresponding builder
factory will be generated. For Rest.li version < 1.24.4, the builder
factory will be named <Resource name>Builders
. For Rest.li version >=
1.24.4, the builder factory is named <Resource name>RequestBuilders
.
The factory contains a factory method for each resource method supported
by the resource. The factory method returns a request builder object
with type-safe bindings for the given method.
Standard CRUD methods are named create()
, get()
, update()
,
partialUpdate()
, delete()
, and batchGet()
. Action methods use the
name of the action, prefixed by “action”, action<ActionName>()
. Finder
methods use the name of the finder, prefixed by “findBy”,
findBy<FinderName>()
.
An example for a resource named “Greetings” is shown below. Here is the builder factory for Rest.li < 1.24.4:
public class GreetingsBuilders {
public GreetingsBuilders()
public GreetingsBuilders(String primaryResourceName)
public GreetingsCreateBuilder create()
public GreetingsGetBuilder get()
public GreetingsUpdateBuilder update()
public GreetingsPartialUpdateBuilder partialUpdate()
public GreetingsDeleteBuilder delete()
public GreetingsBatchGetBuilder batchGet()
public GreetingsBatchCreateBuilder batchCreate()
public GreetingsBatchUpdateBuilder batchUpdate()
public GreetingsBatchPartialUpdateBuilder batchPartialUpdate()
public GreetingsBatchDeleteBuilder batchDelete()
public GreetingsDoSomeActionBuilder actionSomeAction()
public GreetingsFindBySearchBuilder findBySearch()
}
Here is the builder factory for Rest.li >= 1.24.4:
public class GreetingsRequestBuilders extends BuilderBase {
public GreetingsRequestBuilders()
public GreetingsRequestBuilders(String primaryResourceName)
public GreetingsCreateRequestBuilder create()
public GreetingsGetRequestBuilder get()
public GreetingsUpdateRequestBuilder update()
public GreetingsPartialUpdateRequestBuilder partialUpdate()
public GreetingsDeleteRequestBuilder delete()
public GreetingsBatchGetRequestBuilder batchGet()
public GreetingsBatchCreateRequestBuilder batchCreate()
public GreetingsBatchUpdateRequestBuilder batchUpdate()
public GreetingsBatchPartialUpdateRequestBuilder batchPartialUpdate()
public GreetingsBatchDeleteRequestBuilder batchDelete()
public GreetingsDoSomeActionRequestBuilder actionSomeAction()
public GreetingsFindBySearchRequestBuilder findBySearch()
}
In Rest.li < 1.24.4, the generated GET request builder for a resource
is named <Resource>GetBuilder
. In Rest.li >= 1.24.4, the generated
GET request builder is named <Resource>GetRequestBuilder
. Both support
the full interface of the built-in GetRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
e.g., for a parent pathKey named “groupId” of type Integer
in the
“Contacts” resource, the binding method in Rest.li < 1.24.4 would be:
public ContactsGetBuilder groupIdKey(Integer key)
In Rest.li >= 1.24.4, it would be:
public ContactsGetRequestBuilder groupIdKey(Integer key)
In Rest.li < 1.24.4, the generated BATCH_GET request builder for a
resource is named <Resource>BatchGetBuilder
. The generated builder
supports the full interface of the built-in BatchGetRequestBuilder
.
In Rest.li >= 1.24.4, the generated BATCH_GET request builder for a
resource is named <Resource>BatchGetRequestBuilder
. The generated
builder extends the built-in BatchGetEntityRequestBuilder
.
When building requests with BatchGetRequestBuilder
, use the
buildKV()
method (build()
is deprecated), for example:
new FortunesBuilders().batchGet().ids(...).buildKV()
When building requests with the BatchGetEntityRequestBuilder
, the
build()
method is used.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
For example, a parent pathKey named “groupId” of type Integer
in the
“Contacts” resource will have the binding method in Rest.li < 1.24.4 be
this:
public ContactsBatchGetBuilder groupIdKey(Integer key)
In Rest.li >= 1.24.4, it would be:
public ContactsBatchGetRequestBuilder groupIdKey(Integer key)
In Rest.li < 1.24.4, the generated FINDER request builder for a
resource is named <Resource>FindBy<FinderName>Builder
, while in
Rest.li >= 1.24.4 it is named
<Resource>FindBy<FinderName>RequestBuilder
. Both builders support the
full interface of the built-in FindRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
The generated builder will contain a method to set each of the finder’s query parameters, of the form:
public <BuilderType> <paramName>Param(<ParamType> value);
The value must be non-null.
If the finder specifies AssocKey
parameters, the builder will contain
a method to set each of them, of the form:
public <BuilderType> <assocKeyName>Key(<AssocKeyType> value);
In Rest.li < 1.24.4, the generated CREATE request builder for a
resource is named <Resource>CreateBuilder
. The generated builder
supports the full interface of the built-in CreateRequestBuilder
.
In Rest.li >= 1.24.4, the generated CREATE request builder for a
resource is named <Resource>CreateRequestBuilder
. The generated
builder extends the built-in CreateIdRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
If @ReturnEntity
annotation is specified for CREATE implementation, an
additional CreateAndGet
request builder will be generated. Note that
Create
request builder is still available so that adding
@ReturnEntity
is backward compatible for a Java client.
public class <Resource>RequestBuilders
{
...
public <Resource>CreateRequestBuilder create();
public <Resource>CreateAndGetRequestBuilder createAndGet();
...
}
The response will be of type IdEntityResponse<K, V>
which has a
getEntity()
method:
...
// "greeting" is defined in previous context\
CreateIdEntityRequest\<Long, Greeting\> createIdEntityRequest =
builders.createAndGet().input(greeting).build();
Response\<IdEntityResponse\<Long, Greeting\>\> response =
restClient.sendRequest(createIdEntityRequest).getResponse();
...
IdEntityResponse\<Long, Greeting\> idEntityResponse =
response.getEntity();
// The returned entity from server\
Greeting resultEntity = idEntityResponse.getEntity();
The projection for returned entity is supported.
...
// "greeting" is defined in previous context\
CreateIdEntityRequest\<Long, Greeting\> createIdEntityRequest =
builders.createAndGet().fields(Greeting.fields().tone(),
Greeting.fields().id()).input(greeting).build();
In Rest.li < 1.24.4, the generated BATCH_CREATE request builder for a
resource is named <Resource>BatchCreateBuilder
. The generated builder
supports the full interface of the built-in BatchCreateRequestBuilder
.
In Rest.li >= 1.24.4, the generated BATCH_CREATE request builder for a
resource is named <Resource>BatchCreateRequestBuilder
. The generated
builder extends the built-in BatchCreateIdRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
If @ReturnEntity
annotation is specified for BATCH_CREATE
implementation, an additional BatchCreateAndGet
request builder will
be generated. Note that BatchCreate
request builder will still be
generated so that adding @ReturnEntity
annotation is backward
compatible for a Java client.
public class <Resource>RequestBuilders\
{
...
public <Resource>BatchCreateRequestBuilder batchCreate();
public <Resource>BatchCreateAndGetRequestBuilder batchCreateAndGet();
...
}
The response will be of type BatchCreateIdEntityResponse
whose
elements are CreateIdEntityStatus
object containing the returned
entity. Here is a code example.
// "greetings" is defined in previous context
BatchCreateIdEntityRequest<Long, Greeting> batchCreateIdEntityRequest = builders.batchCreateAndGet().inputs(greetings).build();
Response\<BatchCreateIdEntityResponse<Long, Greeting>> response = restClient.sendRequest(batchCreateIdEntityRequest).getResponse();
BatchCreateIdEntityResponse<Long, Greeting> entityResponses = response.getEntity();
for (CreateIdEntityStatus<?, ?> individualResponse : entityResponses.getElements())
{
Greeting entity = (Greeting)individualResponse.getEntity();// The returned individual entity from server
}
The projection for returned entities is supported.
...
// "greetings" is defined as a list of greeting in previous context\
BatchCreateIdEntityRequest<Long, Greeting> batchCreateIdEntityRequest = builders.batchCreateAndGet().fields(Greeting.fields().tone(),
Greeting.fields().id()).inputs(greetings).build();
In Rest.li < 1.24.4, the generated PARTIAL_UPDATE request builder for
a resource is named <Resource>PartialUpdateBuilder
. Whereas in Rest.li >= 1.24.4, it is called <Resource>PartialUpdateRequestBuilder
. Both
builders support the full interface of the built-in
PartialUpdateRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
See Creating partial updates for details on how to create a request for a partial update.
If the PARTIAL_UPDATE method is annotated with a @ReturnEntity
annotation,
an additional PartialUpdateAndGet
request builder will be generated. Note that
the PartialUpdate
request builder is still available so that adding
@ReturnEntity
is backward compatible for Java clients.
public class <Resource>RequestBuilders
{
...
public <Resource>PartialUpdateRequestBuilder partialUpdate();
public <Resource>PartialUpdateAndGetRequestBuilder partialUpdateAndGet();
...
}
The returned entity will be directly accessible from the response using getEntity()
:
...
// "greeting" is defined in previous context
PartialUpdateEntityRequest<Greeting> partialUpdateEntityRequest = builders.partialUpdateAndGet()
.id(1L)
.input(greeting)
.build();
Response<Greeting> response = restClient.sendRequest(partialUpdateEntityRequest).getResponse();
...
// The returned entity from server
Greeting resultEntity = response.getEntity();
Using projections on the returned entity is supported:
...
// "greeting" is defined in previous context\
PartialUpdateEntityRequest<Greeting> partialUpdateEntityRequest = builders.partialUpdateAndGet()
.fields(Greeting.fields().tone(), Greeting.fields().id())
.id(1L)
.input(greeting)
.build();
In Rest.li < 1.24.4, the generated BATCH_PARTIAL_UPDATE request
builder for a resource is named <Resource>BatchPartialUpdateBuilder
.
Whereas in Rest.li >= 1.24.4, it is
<Resource>BatchPartialUpdateRequestBuilder
. Both support the full
interface of the built-in BatchPartialUpdateRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
In Rest.li < 1.24.4, the generated UPDATE request builder for a
resource is named <Resource>UpdateBuilder
. Whereas in Rest.li >=
1.24.4, it is named <Resource>UpdateRequestBuilder
. Both builders
support the full interface of the built-in UpdateRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
In Rest.li < 1.24.4, the generated BATCH_UPDATE request builder for a
resource is named <Resource>BatchUpdateBuilder
. Whereas in Rest.li >=
1.24.4, it is named <Resource>BatchUpdateRequestBuilder
. Both builders
support the full interface of the built-in BatchUpdateRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
The generated DELETE request builder for a resource is named
<Resource>DeleteBuilder
. The generated builder supports the full
interface of the built-in DeleteRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
In Rest.li < 1.24.4, the generated BATCH_DELETE request builder for a
resource is named <Resource>BatchDeleteBuilder
. Whereas in Rest.li >=
1.24.4, the builder is called <Resource>BatchDeleteRequestBuilder
.
Both builders support the full interface of the built-in
BatchDeleteRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
In Rest.li < 1.24.4, the generated ACTION request builder for a
resource is named <Resource>Do<ActionName>Builder
. Whereas in Rest.li
= 1.24.4, it is
<Resource>Do<ActionName>RequestBuilder
. Both builders support the full interface of the built-inActionRequestBuilder
.
If the resource class is a child resource, the generated builder will include a type-safe path-key binding method for each of the resource’s ancestors (recursively following parent resources). Each binding method is declared as:
public <BuilderType> <pathKeyName>Key(<KeyType> key);
The generated builder will contain a method to set each of the action’s parameters. It Rest.li < 1.24.4, it is of the form:
public <BuilderType> param<ParamName>(<ParamType> value);
In Rest.li >= 1.24.4, it is of the form:
public <BuilderType> <paramName>Param(<ParamType> value);
The value must be non-null.
To call a subresource of the fortunes resource, for example:
GET /fortunes/1/subresource/100
The parent keys can be specified by calling generated setters on the
builder. In this case, the fortunesIdKey()
method, for example:
new SubresourceBuilders().get().fortunesIdKey(1l).id(100l).build()
Parent path keys can also be set directly builder classes using the
setPathKey()
method on the builders classes, for example:
.setPathKey("dest", "dest").setPathKey("src", "src")
The built-in RequestBuilder classes provide generic support for constructing Rest.li requests. This layer is independent of the IDL for specific resources; therefore, the interface does not enforce that only “valid” requests are constructed.
There is one RequestBuilder subclass for each of the Rest.li resource
methods. Each RequestBuilder provides a .build()
method that
constructs a Request
object that can be used to invoke the
corresponding resource method. Each RequestBuilder constructs the
Request
subclass that corresponds to the Rest.li method, for example,
BatchGetRequestBuilder.build()
returns a BatchGetRequest
. The
Request
subclasses allow framework code to introspect the original
type and parameters for a given request.
Each RequestBuilder class supports a subset of the following methods, as appropriate for the corresponding resource method:
header(String key, String value)
- sets a request headeraddCookie(HttpCookie cookie)
- adds a cookieid(K id)
- sets the entity key for the resourceids(Collection<K> ids)
- sets a list of entity keysname(String name)
- sets the name for a named resource methodsetParam(String name, Object value)
- sets a query param named
name
to value
addParam(String name, Object value)
- adds value
to the query
param named name
assocKey(String key, Object value)
- sets an association key
parameterpathKey(String key, Object value)
- sets a path key parameter
(entity key of a parent resource)paginate(int start, int count)
- sets pagination parametersfields(PathSpec... fieldPaths)
- sets the fields projection maskinput(V entity)
- sets the input payload for the requestinputs(Map<K, V> entities)
- sets the input payloads for batch
requestsreturnEntity(boolean value)
- sets the $returnEntity
query parameterThe following table summarizes the methods supported by each RequestBuilder type.
| Request Builder | header | id | ids | name | setParam | addParam | assocKey | pathKey | paginate | fields | input | inputs | returnEntity |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Action | - | - | - | - | - | - | |||||||
| Find | - | - | - | - | - | - | - | - | |||||
| Get | - | -* | - | - | - | - | |||||||
| Create | - | - | - | - | - | -** | |||||||
| Delete | - | -* | - | - | - | ||||||||
| PartialUpdate | - | - | - | - | - | - | -** | ||||||
| Update | - | -* | - | - | - | - | |||||||
| BatchGet | - | - | - | - | - | - | |||||||
| BatchCreate | - | - | - | - | - | -** | |||||||
| BatchDelete | - | - | - | - | - | ||||||||
| BatchPartialUpdate | - | - | - | - | - | -** | |||||||
| BatchUpdate | - | - | - | - | - |
* It is not supported, if the method is defined on a simple resource.
** Supported if the resource method is annotated with @ReturnEntity
. See more about this feature.
Refer to the JavaDocs for specific details of RequestBuilder and Request interfaces.
Rest.li uses a custom format called REST Specification (Restspec) as its interface description language (IDL). The Restspec provides a succinct description of the URI paths, HTTP methods, query parameters, and JSON format. Together, these form the interface contract between the server and the client.
Restspec files are JSON format and use the file suffix *.restspec.json.
At a high level, the restspec contains the following information:
Additional details on the Restspec format may be found in the design documents. The Restspec format is formally described by the .pdsc schema files in “com.linkedin.restli.restspec.* “ distributed in the restli-common module.
The IDL generator is used to create the language-independent interface description (IDL) from Rest.li resource implementations (annotated Java code).
The IDL generator is available as part of the restli-tools JAR, as the
com.linkedin.restli.tools.idlgen.RestLiResourceModelExporterCmdLineApp
class.
For details on how to use the IDL Generator, see Gradle build integration.
RestClient
encapsulates the communication with the remote resource.
RestClient
accepts a Request
object as input and provides a
Response
object as output. The Request
objects should usually be
built using the generated type-safe client
builders. Since the
RestClient
interface is fundamentally asynchronous, the Response
must be obtained through either a ResponseFuture
or a Callback
(both
options are supported).
RestClient
is a simple wrapper around an R2 transport client. For
standalone / test use cases, the transport client can be obtained
directly from R2, for example, using the HttpClientFactory
. If you
wish to use D2, the Client
used by the RestClient
must be a D2
client.
The RestClient
constructor also requires a URI prefix that is
prepended to the URIs generated by the Request Builders. When using D2,
a prefix of "d2://"
should be provided that results in URIs using the
D2 scheme.
The RestClient
future-based interface returns ResponseFuture
, which
implements the standard Future
interface and extends it with a
getResponse()
method. The advantage of getResponse()
is that it is
aware of Rest.li exception semantics, throwing
RemoteInvocationException
instead of ExecutionException
.
The standard pattern for making requests using the RestClient is as follows:
RestClient#sendRequest
method to send the request and get
back a ResponseFuture
ResponseFuture#getResponse
to get the Response
that the
server returned. Note that this call blocks until the server
responds or there is an error!Here is a more concrete example, where a client is making a GET request to the /greetings resource -
// First we build the Request. builders is either a GreetingsBuilder or GreetingsRequestBuilder
Request<Greeting> getRequest = builders.get().id(id).build();
// Send the Request and get back a ResponseFuture representing the response. This call is non-blocking.
ResponseFuture<Greeting> responseFuture = restClient.sendRequest(getRequest);
// Like the standard Java Future semantics, calling getResponse() here IS blocking!
Response<Greeting> getResponse = responseFuture.getResponse();
// Get the entity from the Response
Greeting responseGreeting = getResponse.getEntity();
Look at the com.linkedin.restli.client.Response
interface to see what
other methods are available for use.
There are two major changes:
CreateIdRequestBuilder
, which is the super class for all CREATE
request builders, now returns a CreateIdRequest<K, V>
when the
build()
method is called.BatchCreateIdRequestBuilder
, which is the super class for all
BATCH_CREATE request builders, now returns a
BatchCreateIdRequest<K, V>
when the build()
method is called.Starting with Rest.li 1.24.4, we have introduced a few changes to the
Response
API.
As mentioned in the section above, calling build()
on a
CreateIdRequestBuilder
gives us a CreateIdRequest<K, V>
.
When this is sent using a RestClient
we get back (after calling
sendRequest(...).getResponse().getEntity()
) an IdResponse<K>
that
gives us a single, strongly-typed key.
Similarly, when a RestClient
is used to send out a
BatchCreateIdRequest<K, V>
we get back a BatchCreateIdResponse<K>
,
which contains a List
of strongly-typed keys.
When a BatchGetEntityRequest
is sent using a RestClient
we get back
(after calling sendRequest(...).getResponse().getEntity()
) a
BatchKVResponse<K,EntityResponse<V>>
where K
is the key type and V
is the value (which extends RecordTemplate
) for the resource we are
calling.
EntityResponse
is a RecordTemplate
with three fields:
entity
provides an entity record if the server resource finds a
corresponding value for the key;status
provides an optional status code;error
provides the error details from the server resource
(generally entity
and error
are mutually exclusive as null
,
but it is ultimately up to the server resource).Note that since EntityResponse
contains an error
field, the
Map<K, V>
returned by BatchEntityResponse#getResults()
contains both
successful as well as failed entries. BatchEntityResponse#getErrors()
will only return failed entries.
The response type of the BatchUpdate
series methods are not changed.
However, similar to EntityResponse
, we added a new error
field to
UpdateStatus
(the value type of the BatchUpdate
series methods).
Furthermore, BatchKVResponse<K, UpdateStatus>#getResults()
will
returns both successful as well as failed entries. getErrors()
will
only return failed entries.
The following diagram illustrates the request/response flow for a client/server interaction. The call may fail at any point during this flow, as described below.
The following list describes the failures scenarios as observed by a
client calling ResponseFuture.getResponse()
Failure Scenarios
ServiceUnavailableException
- if D2 cannot locate a node for
the requested service URIRemoteInvocationException
- if R2 cannot connect to the remote
endpoint or send the requestTimeoutException
- if a network failure prevents the request
from reaching the serverRestLiResponseException
- if an error occurs within the
framework, resulting in a non-200 responseTimeoutException
- if an error prevents the server from
sending a responseRestLiResponseException
- if the application throws an
exception the server framework will convert it into a non-200
responseTimeoutException
- if an application error prevents the server
from sending a response in a timely mannerRestLiResponseException
- if an error occurs within the
framework, resulting in a non-200 responseTimeoutException
- if an error prevents the server from
sending a responseTimeoutException
- if a network failure prevents the response
from reaching the clientRestLiDecodingException
- if the client framework cannot
decode the response documentRemoteInvocationException
- if an error occurs within the
client framework while processing the response.Each request sent to a Rest.li server can be configured with custom
options by using an instance of RestliRequestOptions
.
RestliRequestOptionsBuilder
is required to construct an instance of
RestliRequestOptions
. Once constructed, an instance of
RestliRequestOptions
can then be passed to Rest.li generated type-safe
request builders. Subsequently, RestClient
will construct a
RestRequest
based on these custom options to send to the Rest.li
server. Currently we support specifying the following custom options per
Request:
When sending a Request, the caller can specify what protocol version option is to be used. The available ProtocolVersionOption(s) are:
Use the next version of the Rest.li protocol to encode requests,
regardless of the version running on the server. The next version of the
Rest.li protocol is the version currently under development. This option
should typically NOT be used for production services.
CAUTION: this can cause requests to fail if the server does not
understand the next version of the protocol.
“Next version” is defined as
com.linkedin.restli.internal.common.AllProtocolVersions.NEXT_PROTOCOL_VERSION
.
Use the latest version of the Rest.li protocol to encode requests,
regardless of the version running on the server.
CAUTION: this can cause requests to fail if the server does not
understand the latest\
version of the protocol. “Latest version” is defined as
com.linkedin.restli.internal.common.AllProtocolVersions.LATEST_PROTOCOL_VERSION
.
Use the latest version of the Rest.li protocol if the server supports it. If the server version is less than the baseline Rest.li protocol version then fail the request. If the server version is greater than the next Rest.li protocol version then fail the request. If the server is between the baseline and the latest version then use the server version to encode the request. If the server version is greater than or equal to the latest protocol version then use that to encode the request.
com.linkedin.restli.internal.common.AllProtocolVersions.BASELINE_PROTOCOL_VERSION
.com.linkedin.restli.internal.common.AllProtocolVersions.LATEST_PROTOCOL_VERSION
.com.linkedin.restli.internal.common.AllProtocolVersions.NEXT_PROTOCOL_VERSION
.CAUTION: Please be very careful setting the non-default
FORCE_USE_NEXT or FORCE_USE_LATEST options as the protocol
version option in RestLiRequestOptions
, since they may cause requests
to fail if the server does not understand the desired protocol request.
This form of configuration is normally used in migration cases.
When sending a Request, the caller can force compression on or off for each request.
Compress the request.
Do not compress the request.
If null
is specified, Rest.li ClientCompressionFilter
will determine
whether we need to do client side compression based on request entity
length.
When sending a Request, the caller can also specify what content type is to be used. The specified value will be set to the HTTP header “Content-Type” for the request.
This will set “Content-Type” header value as “application/json”.
This will set “Content-Type” header value as “application/x-pson”
NOTE: Besides RestliRequestOption
, the caller can also specify the
ContentType
through the RestClient
constructor by passing the
contentType parameter (as shown below), which will apply to all requests
sent through that client instance.
public RestClient(Client client, String uriPrefix, ContentType contentType, List<AcceptType> acceptTypes)
However, this form of configuration has been DEPRECATED. Please use
RestliRequestOptions
instead to set such custom options. In cases
where the caller has configured content type from multiple places,
RestClient will resolve request content type based on the following
precedence order:
If null
is specified for content type from these 3 sources,
RestClient
will use JSON as default.
When sending a Request, the caller can also specify what media types it can accept. The specified value will be set to the HTTP header “Accept” for the request. If more than one AcceptType is specified, we will generate an Accept header by appending each media type by a “q” parameter for indicating a relative quality factor. For example:
Accept: application/*; q=0.2, application/json
Quality factors allow the user or user agent to indicate the relative degree of preference for that media type, using the scale from 0 to 1. The default value is q=1. In our case, the quality factor generated is based on the order of each accept type we specified in the list. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for details.
This will accept media type of “application/json”.
This will accept media type of “application/x-pson”.
This will accept any media type.
NOTE: Besides RestliRequestOption
, the caller can also specify
AcceptType through the RestClient
constructor by passing the
acceptTypes parameter (as shown below), which will apply to all requests
sent through that client instance.
public RestClient(Client client, String uriPrefix, List<AcceptType> acceptTypes)
public RestClient(Client client, String uriPrefix, ContentType contentType, List<AcceptType> acceptTypes)
However, this form of configuration has been DEPRECATED. Please use
RestliRequestOptions
instead to set such custom options. In cases
where the caller has configured accept types from multiple places,
RestClient will resolve request accept type based on the following
precedence order:
If null
is specified for the accept type from these 3 sources,
RestClient
will not set the HTTP “Accept” header. If no accept header
field is present, then it is assumed by the Rest.li server that the
client accepts all media types based on the HTTP Spec (RFC 2616).
If RestliRequestOptions
is not set, or is set to null, the request
builders will use
RestliRequestOptions.DEFAULT_OPTIONS(ProtocolVersionOption.USE_LATEST_IF_AVAILABLE, null /*compression*/, null /*content type*/, null /*accept type*/)
to generate the Request.
The ParSeqRestClient
wrapper facilitates usage with ParSeq by
providing methods that return a Promise
or a Task
. For example,
users can create multiple requests and use ParSeq to send them in
parallel. This feature is independent of the asynchronous resources; in
particular, the server resource does not have to be asynchronous.
ParSeqRestClient client = new ParSeqRestClient(plain rest client);
// send some requests in parallel\
Task<Response<?>> task1 = client.createTask(request1);
Task<Response<?>> task2 = client.createTask(request2);
Task<Response<?>> combineResults = ...;
// after we get our parallel requests, combine them\
engine.run(Tasks.seq(Tasks.par(task1, task2), combineResults))
Users of createTask
are required to instantiate their own ParSeq
engine and start the task themselves.
As described above, the Rest.li client framework includes a code-generation tool that creates type-safe Request Builder classes based on resource IDL files.
The code generator is available as part of the restli-tools JAR, as
com.linkedin.restli.tools.clientgen.RestRequestBuilderGenerator
. The
generator is invoked by providing an output directory and a list of
input IDL files as command-line arguments.
In addition, the generator recognizes the following system properties:
generator.rest.generate.datatemplates
- boolean property
indicating whether the generator should generate Java RecordTemplate
classes for the .pdsc schemas referenced by the IDL file.generator.default.package
- the default package name for generated
classesgenerator.resolver.path
- a colon-separated list of filesystem
paths to search when resolving references to named schemas. See
“Data Template Generator” for more details.The Rest.li client code generator is integrated as part of the pegasus
gradle plugin. For details, see Gradle build
integration.
Rest.li can be used with the D2 layer for dynamic discovery and
client-side load balancing. The use of D2 is normally transparent at the
Rest.li layer. However, for applications wishing to make more
sophisticated use of Rest.li and D2, the restli-extras
module is
provided.
The main feature supported in restli-extras
is the ability to make
parallel “scatter/gather” requests across all the nodes in a cluster.
Currently, scatter/gather functionality is only supported for BATCH_GET
methods.
Scatter/gather makes use of D2’s support for consistent hashing, to
ensure that a given key is routed to the same server node when possible.
The ScatterGatherBuilder
interface can be used to partition a single
large BatchGetRequest
into N
BatchGetRequests
, one for each node
in the cluster. The key partitioning is done according to the D2
consistent hashing policy, using a KeyMapper
object obtained from the
D2 Facilities
interface. Batch updates and deletes are also supported.


{% extends "../../layouts/guidance.html" %} {% set sectionId = "community" %} {% set sectionTitle = "Community" %} {% set pageId = "design-system-working-group" %} {% set pageTitle = "Design system working group" %} {% block subNav %} {% include "../navigation.html" %} {% endblock %} {% block pageContent %}
The Design System Working Group is a multidisciplinary panel of representatives from across the DVSA. It makes sure that all components and patterns published in the Design System are of a high quality and meet user needs.
The working group:
By including representatives from a mixture of disciplines and departments, the working group helps to ensure that the Design System represents its users.
It means that decisions made are fair and unbiased, and that the Design System reflects the experiences of the whole of DVSA, not just one agency or service team.
The DVSA Design System team is looking into the best way of helping people to join the working group.
In the meantime if you are interested in joining the working group email the Design System team at design-system@dvsa.gov.uk.


Over the years many adventures have entered the Minotaur’s Labyrinth in an effort to kill the Minotaur. Their quests for glory and riches have always failed and the Minotaur has gotten richer from the gold of each failed adventurer. The evil Minotaur has been hoarding the land's gold for years, not leaving enough for the town's economy to thrive. You have decided to enter the Minotaur’s Labyrinth in order to steal the treasure. Your goal is to steal the treasure and make it to the red teleportation seal without getting spotted by the Minotaur. The teleportation seal will appear once you have collected the treasure. But watch out: once you have the treasure the Minotaur will become enraged and chase after you. Most adventurers have set out to kill the Minotaur but you know that the labyrinth is enchanted so that the Minotaur cannot die within its walls.
Our brave hero.
The Minotaur, can't be killed while in the labyrinth.
Energy Potion, increases strenght & health.
Gargoyle statue, immoveable and blocks your path.
Teleportation seal, use this to escape the labyrinth.
The Minotaur’s treasure chest, collect this and escape the labyrinth.


|
||||||||||
| PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |
See:
Description
| Class Summary | |
| AttributeListImpl | Deprecated. This class implements a deprecated interface,
AttributeList ;
that interface has been replaced by
Attributes ,
which is implemented in the
AttributesImpl helper class. |
| AttributesImpl | Default implementation of the Attributes interface. |
| DefaultHandler | Default base class for SAX2 event handlers. |
| LocatorImpl | Provide an optional convenience implementation of Locator. |
| NamespaceSupport | Encapsulate Namespace logic for use by SAX drivers. |
| ParserAdapter | Adapt a SAX1 Parser as a SAX2 XMLReader. |
| ParserFactory | Deprecated. This class works with the deprecated
Parser
interface. |
| XMLFilterImpl | Base class for deriving an XML filter. |
| XMLReaderAdapter | Adapt a SAX2 XMLReader as a SAX1 Parser. |
| XMLReaderFactory | Factory for creating an XML reader. |
This document is in the PUBLIC DOMAIN and comes with NO WARRANTY of any kind.
This is a prerelease of a bugfix release for SAX2, the second generation of the Simple API for XML. For information, see docs/index.html.
|
||||||||||
| PREV PACKAGE NEXT PACKAGE | FRAMES NO FRAMES |


100-year-old woman dies an hour after 104-year-old husband passes away
The couple Vetrivel, 104, and Pichayi, 100, were residents of Kuppakkudi Adi Dravidar Colony under Alangudi Taluk and had been married for 75 years.
by mid-day online correspondentChennai: A 100-year-old woman died an hour after her 104-year-old husband passed away in Tamil Nadu's Pudukkottai district on Monday.
The couple Vetrivel, 104, and Pichayi, 100, were residents of Kuppakkudi Adi Dravidar Colony under Alangudi Taluk and had been married for 75 years, according to a report in Hindustan Times. It also said that the two were in good health.
Vetrivel complained of chest pain on Monday night after which, his relatives rushed him to a nearby hospital in Alangudi, but he was declared dead on arrival. When his mortal remains were taken back to Kuppakkudi, his wife broke down.
“Our grandma cried before our grandfather’s body and fainted. When we shook her to revive her, she did not respond. So, we called a local doctor to check her pulse. The doctor confirmed that our grandma was no more, less than an hour after our grandfather died,” said L Kumaravel, one of the grandsons of the elderly couple, was quoted as saying in the report.
Apart from 23 grandchildren and several great-grandchildren, the couple is survived by five sons and one daughter.
Sign up for all the latest news, top galleries and trending videos from Mid-day.com
Subscribe
Loading...
Mumbai: 65-year-old woman dragged on train platform while boarding


Checkbox Group Plugin Demo
Just showing how markup doesen't really matter:
Apple
Banana
Pear
Validate!


Your browser doesn’t support the JavaScript functions required. The latest versions of Firefox, Chrome, Safari, and MS Edge are known to work, and Internet Explorer 11 does too. Try one of them instead.


Understanding teams with
Watership Down
A group of individuals is not a team.
They can become a team.
If they have a common GOAL
The goal should be:
- discussed within the team
- explicit rather than implicit
- compatible with organisation goal
- supported by the organisation
- measurable
- doable
Roles cover these areas:
- result
- methodology
- innovation
- communication
Issues around roles:
- missing key areas
- leader-does-all
- member without role
Managing conflicts (some guidelines):
- never ignore
- do not blame but find causes
- negotiate and not barter
- talk facts and not opinions
- value different point of view
- value common points
- do not put proposal in conflict, but in relation
Emerging team characteristics:
Openess: how neat are the boundaries of the team?
Cohesion: do we share the same norms and values ?
Interdependence: how much we depend each other?
Fragmentation: can we recognise subgroups in the team?
Specialisation: are we able to cover all necessary functions?
Integration: do we accept each other ideas?
Do not use rabbit language with birds!
Take into consideration culture, roles, context
Institutional leader
Responsible for team's output, goal and resources
Leadership issues
- Lack of competence
- Tyrant leader
- Different from team expectations
Epilogue


Settings
User Settings
Language
English (US)
English (UK)
English (CA)
English (AU)
English (IN)
Portuguese (BR)
Portuguese (PT)
Spanish (ES)
Spanish (AR)
Spanish (CO)
Spanish (CL)
Spanish (MX)
Chinese (CN)
Chinese (TW)
Currency
U.S Dollar (US$)
Euro (€)
Pound (£)
Brazilian Real (R$)
Chinese Yuan
Units
Miles (ft²)
Kilometers (m²)
Notifications?
App Settings
Clear Private Data
Push Notifications?
Privacy Policy


{% extends "base.html" %} {% block title %} List of Students by Courses {% endblock %} {% block content %}
| Students in {{ sel_courses }} | |
|---|---|
| between {{ from_date }} and {{ to_date }} | |
| WSU ID | |
| {{forloop.counter}} | {{stu.wsu_id}} |
No students found.
{% endif %} {% endif %}


RT @phonescooper: Dear @Budweiser: Black Crown amber lager? You will never make good beer. Just stop.
2/3/2013, 7:08:17 PM
Favs: 0
Retweets: 0
link--- layout: tweet ---
RT @phonescooper: Dear @Budweiser: Black Crown amber lager? You will never make good beer. Just stop.
2/3/2013, 7:08:17 PM
Favs: 0
Retweets: 0
link


1. Choose your source
2. Choose or create a Configuration
3 Click "Import Data"
4. Pay Attention to the Log
5. Wait until the data is Uploaded
6. Rows with errors can be downloaded with the "Get Skipped Rows" button
Depending on which file you choose there will be different steps to work off.
1. Your source is a .csv file
1.1 Delimiter:
The delimiter separates the columns of a csv file. Search the delimiter and write it into the
textarea.
An often used delimiter is ";".
1.2 Number of header lines:
Mostly there are some lines at the top of your table that you don't want to import. Count this lines
and write this number
in the textarea.
1.3 Current mapping:
If there are values in a column you want to replace, than you can use the mapping.
Choose a column where you want to apply the mapping.
At "old value" you write the value you want to replace.
At "Replaced by" the value you want to replace the old value.
1.4 Datetime: Choose a timezone and columns where the date and time can be found. Enter a format, so that the program can recognize the date and time
1.5 Select or create a thing
1.6 Select or create a datastream
2. Your source is an excel file
1.1 Delimiter:
A delimiter is not needed.
1.2 Number of header lines:
Mostly there are some lines at the top of your table that you don't want to import. Count this lines
and write this number
in the textarea.
1.3 Current mapping:
If there are values in a column you want to replace, than you can use the mapping.
Choose a column where you want to apply the mapping.
At "old value" you write the value you want to replace.
At "Replaced by" the value you want to replace the old value.
1.4 Datetime: Choose a timezone and a column where the date and time can be found. You don't need to enter a format.
1.5 Select or create a thing
1.6 Select or create a datastream
The preview shows the the first 3 lines of your file. It may help you to create a Configuration.
A file in "Get Skipped Rows" includes all lines, which can't be imported in the FROST-Server. You may want to edit your Configuration and try to upload this missing lines.
In the mapping table you can save if you want to replace a value with an other value in a column. The column scope you can enter a column where you want to apply the mapping. The old value is the value you want to replace. In the "replaced by" scope you enter the value which will replace the old value.
An example: You want to replace every value "midnight" with the value "00:00:00" in column 5.
What you have to do:
1. Add a row/mapping in the mapping popup
2. Column = 5
3. Old value = "midnight"
4. Replaced by = "00:00:00"
5. Click save
6. Close popup


Damage Functions (DFs), also known as Vulnerability Functions, associate the physical damage to a building or a structure (and also its contents and inventory) from natural disasters to financial damage. The Federal Emergency Management Agency (FEMA) in USA developed several thousand DFs and these serve as a benchmark in natural catastrophe modeling, both in academia and industry. However, these DFs and their documentation are buried within the HAZUS software are not easily accessible for analysis and visualization. This package provides more than 1300 raw DFs used by FEMA's HAZUS software and also functionality to extract and visualize DFs specific to the flood hazard. The vignette included with this package demonstrates its use.
| Version: | 0.1 |
| Depends: | R (≥ 3.0.2) |
| Imports: | reshape2 |
| Suggests: | ggplot2, knitr |
| Published: | 2014-06-19 |
| Author: | Gopi Goteti |
| Maintainer: | Gopi Goteti <my.ration.shop at gmail.com> |
| License: | GPL-2 | GPL-3 [expanded from: GPL (≥ 2)] |
| NeedsCompilation: | no |
| Materials: | NEWS |
| CRAN checks: | hazus results |
| Reference manual: | hazus.pdf |
| Vignettes: |
Package Demo: hazus |
| Package source: | hazus_0.1.tar.gz |
| Windows binaries: | r-devel: hazus_0.1.zip, r-release: hazus_0.1.zip, r-oldrel: hazus_0.1.zip |
| OS X binaries: | r-release: hazus_0.1.tgz, r-oldrel: hazus_0.1.tgz |
Please use the canonical form https://CRAN.R-project.org/package=hazus to link to this page.


17BCS2154 (SHAGUN TIWARI) & (VIKALP UPADHYAY) 17BCS2150
THE INFLUENCER
Home
Contact
About
Give details to VOTE !
Name
Country
Phone
Designation


Press Releases
- 05 October 2015 - Version 3.0.10 has been released.
The focus of this release is stability.
- 30 September 2015 - Version 2.2.9 has been released.
The focus of this release is stability.
- 09 July 2015 - Version 2.2.8 has been released.
The focus of this release is stability.
- 08 July 2015 - Version 3.0.9 has been released.
The focus of this release is stability.
- 22 April 2015 - Version 3.0.8 has been released.
The focus of this release is stability.
- 22 April 2015 - Version 2.2.7 has been released.
The focus of this release is stability.
- 19 February 2015 - Version 3.0.7 has been released.
The focus of this release is stability.
- 17 December 2014 - Version 3.0.6 has been released.
The focus of this release is stability.
- 21 November 2014 - Version 3.0.5 has been released.
The focus of this release is stability.
- 18 November 2014 - Version 2.2.6 has been released.
The focus of this release is stability.
- 10 Sept 2014 - Version 3.0.4 has been released.
The focus of this release is stability.
- 12 May 2014 - Version 3.0.3 has been released.
The focus of this release is stability.
- 28 April 2014 - Version 2.2.5 has been released.
The focus of this release is stability.
- 21 March 2014 - Version 3.0.2 has been released.
The focus of this release is stability.
- 19 March 2014 - Version 2.2.4 has been released.
The focus of this release is stability.


{%extends 'layout.html'%} {%block body%}
We are software engineers who like to listen smooth tunes while working. It is scientifically proven that
listening
smooth tunes while working can help you in several ways. We thought why do not we share this beautiful
collection with you. This tunes will provide you ambiance of good environments.
Some advantages we would like to mention.


{% extends "base.html" %} {% block content %}
Set up your streaming system to receive streamlabs alerts. Simply download Streamlabs OBS and install it. Customise Streamlabs OBS afterwards.
If you prefer other programs, here is a guide for OBS Studio and Xsplit can be found here: Adding widgets to OBS/Xsplit


Akan Name Generator
Akan names are derived from Ghanian culture. Frequently in Ghana,
children are given their first name as a 'day name'
which corresponds to the day in the week they were born. This site helps generate these names.Want to know your Akan name?


webrtc
web real time communication platform
This is a ready to deploy webrtc SDK and SaaS for a customized and flexible communication and collaboration solution .
SDK
Architecture
The Solution primarily contains nodejs frameworks for hosting the project and webbsockets over socket.io to perform offer - answer handshake and share SDP (Session description protocol ).
Technologies used
WebRTC Web based real time communication framework. read more on webrtc
Node ( v10.0.0) Asynchronous event driven JavaScript runtime
socket.io ( v0.9) Communication and signalling
Note : while its possible to use any prtotocol like SIP , XMPP , AJAX , JSON etc for this purpose , modifying thsi libabry will require a lot of rework . It would be advisble to start from apprtc directly in that case .
- Grunt
It is a task Runner and its used to automate running of command in gruntfile
grunt -verbose
Build SDK
Project is divided into 4 parts
- Core RTC Conn Lib
- Wrappers for the Core Lib containing feature sets and widgets like screensharing , recording , pointer share , machine learning , face detection etc
- Demo Applicatins like two party full-features , multi-party full features etc which implement and use the SDK by invoking the constructirs , emitters and listeners .
Building the SDK
Download the dev dependencies by setting the NODE_ENV to dev . This will install all grunt and gulp dependencies used for building the SDK
NODE_ENV=development npm install
To build the RtcConn , outputs RTCMultiConn
grunt rtcconn
To build the webrtcdev lib . It encapsulates the rtcconn core along with external libs for building various custom features . Outputs webrtcdevelopment.js , webrtcdevelopment_header.js , webrtcdevelopment.css , webrtcdevelopment_header.css and webrtcdevelopmentserver.js
gulp production
Get Started
To run this project following steps need to be followed in that order :
1. get the project from github
git clone https://github.com/altanai/webrtc.git webrtc
2. install nvm ( node version manager )
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.2/install.sh | bash
. ~/.nvm/nvm.sh
nvm install v5.0.0
nvm use v5.0.0
3. install npm and update the dependencies
It will read the package.json and update the dependencies in node_modules folder on project location
sudo apt-get install npm
npm install
4. Change ENV variables and Test
To change the ports for running the https server and rest server, goto env.json
{
"hostname" : "host",
"enviornment" : "local",
"host" : "localhost",
"jsdebug" : true,
"httpsPort" : 8086,
"restPort" : 8087,
"extensionID" : "elfbfompfpakbefoaicaeoabnnoihoac"
}
To run the tests
npm test
5. Start up the Server
To start the Server in dev mode and stop the server as soon as Ctrl+ C is hit or the terminal widnow is closed .
node webrtcserver.js
read more about node
To start the Server using npm start ( using package.json) , behaves same as earlier run using node. We use supervisor to restart the server incase of exceptions or new code .
npm start
6. JS and CSS Libs
Make a webpage and give holders for video and button elements that SDK will use .
Inside the head tag of html build/webrtcdevelopment_header.css build/webrtcdevelopment_header.js
After the body tag of html build/webrtcdevelopment.css build/webrtcdevelopment.js
7. Configure
Create the webrtc dom object with local and remote objects
local object :
var local={
video : "myAloneVideo", // name of the local video element
videoClass : "", // class of the localvideo
videoContainer : "singleVideoContainer", // outer container of the video element
userDisplay : false, // do you want to display user details
userMetaDisplay : false,
userdetails:{ // users details include name , email , color
username : username,
usercolor : "#DDECEF",
useremail : useremail,
role : "participant" // role of user in the session , can be participant , admin , inspector
}
};
remote object :
var remote={
videoarr : ["myConferenceVideo", "otherConferenceVideo"], // conatiners for the video after session is made
// first one is usually the local video holder followed by remote video holders
videoClass : "",
maxAllowed : "6",
videoContainer : "confVideoContainer",
userDisplay : false,
userMetaDisplay : false,
dynamicVideos : false
};
Incoming and outgoing media configiration ( self exlanatory ) :
var incoming={
audio : true,
video : true,
data : true,
screen: true
};
var outgoing={
audio : true,
video : true,
data : true,
screen: true
};
webrtcdomobj= new WebRTCdom(
local, remote, incoming, outgoing
);
Get session id automaically
sessionid = init(true);
or get session name from user
sessionid = init(false);
Create a session json object with turn credentials nd the session created from above step
Set widgets and their properties . Currently available widgets are
* Chat
* Fileshare
* Timer
* Screen Record
* Screen Share
* Video Record
* Snapshot
* Minimising/ maximising Video
* Mute (audio and/or video)
* Draw on Canvas and Sync
* Text Editor and Sync
* Reconnect
Description of Widgets with SDK invocation
1. Chat
User RTCDataConnection api from webRTC to send peer to peer nessages within a session. If camera is present the SDK captures a screenshot with user's cemars feed at the isnatnt of typing the message and send along with the message.
When the chat widget is active , if the dom specified by the container id is present then webSDK uses as it is, else it creates one default box
{
active: true,
container: {
id: "chatContainer" // dom id of the chat conatiner
},
inputBox:{
text_id:"chatInputText", // dom id of the chta's input box
sendbutton_id:"chatSendButton", // dom id for the chat's send button
minbutton_id:"minimizeChatButton" // dom id for minimizing the Chat conaginer
},
chatBox:{
id: "chatBoard" // dom id for the chat board where all messages are dispalyed
},
button:{ // on and off button states for the chat widget button
class_on:"btn btn-warning glyphicon glyphicon-font topPanelButton",
html_on:"Chat",
class_off:"btn btn-success glyphicon glyphicon-font topPanelButton",
html_off:"Chat"
}
}
Upcoming : Adding emoticons to Chat
2. Fileshare
Uses the RTCDataConnection API from WebRTC to excahnge files peer to peer. Progress bar is displayed for the chunks of file transferrred out of total number of chunks. Many different kindes of file transfer have been tested such as media files ( mp3 , mp4 ) , text or pdf files , microsoft pr libra office dicuments , images ( jpg , png etc ) etc .
File share widgets creates uses 2 conatiners - File Share and File List . If the dom ids of the container are not present on the page , the SDK crestes default conainers and appends them to page
The list of files with buttons to view , hide or remove them from file viewers are in file Viewer container . Displaying or playing the text or media files happens in file share conainer , which also has button to maximize , minimize the viewer window or in case of images to rotate them.
{
active : true,
fileShareContainer : "fileSharingRow", // File sharing container
fileshare:{ // components of file sharing container
rotateicon:"assets/images/refresh-icon.png", // rotate icon
minicon:"assets/images/arrow-icon-down.png", // min icon
maxicon:"assets/images/arrow-icon.png", // max icon
closeicon:"assets/images/cross-icon.png" // close icon
},
fileListContainer : "fileListingRow", // File List container container
filelist:{ // components of file list conainer
downloadicon:"", // icon donwload
trashicon:"", // icon trash
saveicon:"", // icon save
showicon:"", // icon show
hideicon:"", // icon hide
},
button:{
id: "fileshareBtn", // dom for widget button to call file share
class_on: "col-xs-8 text-center fileshareclass",
html:"File"
},
props:{
fileShare:"divided", // Can be divided for two particiapnts , chatpreview , single for many participants , hidden
fileList:"divided" // same as aboev Can be divided , single , hidden
}
}
3. Timer
Creates or assigns a timer for teh ongoing sesssion . Also displays the geolocation and timezone of the peers if perssion if provided . Timer can start upwards or downwards. Can be used for billing and policy control .
{
active: true,
type: "forward", // Forwards timer starts from 0:0:00 goes thereafter, backward timer ticks backword from prespecified time limit
counter:{
hours: "countdownHours", // dom id for hours
minutes:"countdownMinutes", // dom id for mins
seconds :"countdownSeconds" // dom if for seconds
},
upperlimit: { // upperlimit of time for the session
hour:0 ,
min: 3 ,
sec: 60
},
span:{ // dom ids for local and remote time labels
currentTime_id:"currentTimeArea",
currentTimeZone_id:"currentTimeZoneArea",
remoteTime_id :"remoteTimeArea",
remoteTimeZone_id:"remoteTimeZoneArea",
class_on:""
},
container:{
id:'collapseThree',
minbutton_id:'timerBtn'
},
button :{
id: 'timerBtn' // dom for widget timer button to call
}
}
4. Screen Record
Records everything pesent on the tab selected along with audio and displays recording as mp4 file. Use an extension and pre-declared safe-site to facilitate captuing the tab.
{
active : true,
videoRecordContainer: true, // container for storing or displaying recorded video
button:{ // button to control screen control wisget and its on / off states
id: "scrRecordBtn",
class_on:"btn btn-lg screenRecordBtnClass On",
html_on:'',
class_off:"btn btn-lg screenRecordBtnClass Off",
html_off: ''
}
},
5. Screen-share
One of the most powerful features of the SDK is to capture the current screen and share it with peer over RTC Peer connection channel. Simmilar to csreen record , uses an extension and pre-declared site ownership to capture the screen and share as peer to peer stream . Button for screen share has 3 states -
- install button for inline isnatllation of extension from page ,
- share screen button and
- view button for incoming screen by peer .
{
active : true,
screenshareContainer: "screenShareRow", // container to display screen being shared
extensionID: props.extensionID, // extension id
button:{
installButton:{ // widget button to start inline installation of extension
id:"scrInstallButton",
class_on:"screeninstall-btn on",
html_on:"Stop Install",
class_off:"screeninstall-btn off",
html_off:"ScreenShare"
},
shareButton:{ // widget button to start sharing screen , deactivated once already active or when peer is sharig
id:"scrShareButton",
class_on:"btn btn-lg on",
html_on:'',
class_off:"btn btn-lg off",
html_off:'',
class_busy:"btn btn-lg busy",
html_busy:''
},
viewButton:{ // button to view the icnoming screen share
id:"scrViewButton",
class_on:"screeninstall-btn on",
html_on:"Stop Viewing",
class_off:"screeninstall-btn off",
html_off:"View Screen"
}
}
}
6. Video Record
Records video stream . Created for each peer video .
{
active : true,
videoRecordContainer : true,
button:{
class_on:"pull-right btn btn-modify-video2_on videoButtonClass on",
html_on:"",
class_off:"pull-right btn btn-modify-video2 videoButtonClass off",
html_off:""
}
}
7. Snapshot
Takes a snapshot from video stream . Will be created for each inidvidual peer video .
{
active : true,
snapshotContainer: true,
button:{
class_on:"pull-right btn btn-modify-video2 videoButtonClass",
html_on:""
}
}
8. Minimising/ maximising Video
To enable the user to watch video in full screen mode or to inimize the video to hide it from screen. Will be seprately created for each individual peer video .
{
active : true,
max:{
button:{ // button to maximise the video to full screen mode
id: 'maxVideoButton',
class_on:"pull-right btn btn-modify-video2 videoButtonClass On",
html_on:"",
class_off:"pull-right btn btn-modify-video2 videoButtonClass Off",
html_off:""
}
} ,
min :{
button:{ // button to minimize or hide the video
id: 'minVideoButton',
class_on:"pull-right btn btn-modify-video2 videoButtonClass On",
html_on:"",
class_off:"pull-right btn btn-modify-video2 videoButtonClass Off",
html_off:""
}
}
}
9. Mute (audio and/or video)
Mutes the audio or video of the peer video . Created for each peer video.
{
active: true,
drawCanvasContainer: "drawBoardRow",
container:{
id:'drawContainer',
minbutton_id:'minimizeDrawButton'
},
button:{
id: "draw-webrtc" ,
class_on:"btn btn-lg draw-webrtc On",
html_on:'',
class_off:"btn btn-lg draw-webrtc Off",
html_off:''
}
}
10 . Reconnect
Allows a user to recoonect a session without refreshing a page . Will enable him to drop the session and create a new one.
{
active: false,
button:{
id: "reconnectBtn",
class:"btn btn-success glyphicon glyphicon-refresh topPanelButton",
html:"Reconnect",
resyncfiles:false
}
}
11. Cursor
{
active : true,
pointer:{
class_on: "fa fa-hand-o-up fa-3x"
},
button:{
id: 'shareCursorButton',
class_on:"pull-right btn btn-modify-video2 videoButtonClass On",
html_on:"",
class_off:"pull-right btn btn-modify-video2 videoButtonClass Off",
html_off:""
}
},
12. Inspector
{ active: true, button:{ id:"ListenInButton", textbox : "listenInLink" } }
13. Debug
debug : false,
14. Help
{
active : true ,
helpContainer : "help-view-body",
screenshotContainer: "help-screenshot-body",
descriptionContainer: "help-description-body"
}
15. Stats
{
active : true ,
statsConainer : "network-stats-body"
}
Assign individual widgets to a json object called widgets
var widgets={
chat : < add chat widget json >,
fileshare : < fieshare widget>,
debug : false,
reconnect :{
active: false,
button:{
id: "reconnectBtn",
class:"btn btn-success glyphicon glyphicon-refresh topPanelButton",
html:"Reconnect",
resyncfiles:false
}
},
timer :{
active: true,
type: "forward",
counter:{
hours: "countdownHours",
minutes:"countdownMinutes",
seconds :"countdownSeconds"
},
upperlimit: {
hour:0 ,
min: 3 ,
sec: 60
},
span:{
currentTime_id:"currentTimeArea",
currentTimeZone_id:"currentTimeZoneArea",
remoteTime_id :"remoteTimeArea",
remoteTimeZone_id:"remoteTimeZoneArea",
class_on:""
},
container:{
id:'collapseThree',
minbutton_id:'timerBtn'
},
button :{
id: 'timerBtn'
}
},
chat : < chat widget >
fileShare :< file share widget >
mute : < mute unmute widget >
videoRecord : < video record widget >
snapshot : < snapshot widget >
cursor : < widget for cursor sharing >
minmax : < widget to maximize or minimize the video >
drawCanvas : < draw widget >
screenrecord : < screen record widget >
screenshare : < screen share >
listenin : < listen in widget >
help : {
active : true ,
helpContainer : "help-view-body",
screenshotContainer: "help-screenshot-body",
descriptionContainer: "help-description-body"
},
statistics:{
active : true ,
statsConainer : "network-stats-body"
}
}
Initiate the webrtcdev contructor
webrtcdevobj = new WebRTCdev( local, remote, incoming, outgoing ,session , widgets)
Start the session
startcall();
Implement evenet listners
onLocalConnect
onSessionConnect
onScreenShareStarted
onScreenShareSEnded
onNoCameraCard
8. Add the Key and certs
openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout ssl_certs/server.key -out ssl_certs/server.crt -subj "/CN=webrtc.altanai.com" -days 3650
9. VOIP calls and conf
open tab on chrome or mozilla browser and add a link to the https server using nodejs script https://<IP_ADDRESS>:8086/multiparty_fullfeatures.html
Used Libs
Following are the additioanl libraries packed with the project
Task Runner
you can run gulp alone to minify and concat the js and css files into minscripts
gulp
or can run grunt to concat , minifify , ugligy , push to git and npm all together
grunt production
forever
Keeps running even when window is not active
cd WebCall
forever start webrtcserver.js
Developing / Aletring the Libs
creating doc
./node_modules/.bin/esdoc
open ./docs/index.html
PM2
To start the Server using PM2 ( a process manager for nodejs)
License
MIT


|
| | Builder (TCheckboxGroup component) |
| |
| virtual TBuilder | Listeners (Action< CheckboxGroupListeners > action) |
| | Client-side JavaScript Event Handlers More...
|
| |
| virtual TBuilder | DirectEvents (Action< CheckboxGroupDirectEvents > action) |
| | Server-side Ajax Event Handlers More...
|
| |
| | Builder () |
| |
| | Builder (CheckboxGroup component) |
| |
| | Builder (CheckboxGroup.Config config) |
| |
| | Builder (TCheckboxGroupBase component) |
| |
| virtual TBuilder | AllowBlank (bool allowBlank) |
| | False to validate that at least one item in the group is checked (defaults to true). If no items are selected at validation time, BlankText will be used as the error text. More...
|
| |
| virtual TBuilder | BlankText (string blankText) |
| | Error text to display if the AllowBlank validation fails (defaults to 'You must select at least one item in this group') More...
|
| |
| virtual TBuilder | ColumnsNumber (int columnsNumber) |
| | Specifies a number of columns will be created and the contained controls will be automatically distributed based on the value of vertical. More...
|
| |
| virtual TBuilder | ColumnsWidths (double[] columnsWidths) |
| | You can also specify an array of column widths, mixing integer (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will be rendered first, then any float values will be calculated as a percentage of the remaining space. Float values do not have to add up to 1 (100%) although if you want the controls to take up the entire field container you should do so. More...
|
| |
| virtual TBuilder | FireChangeOnLoad (bool fireChangeOnLoad) |
| | Fire change event after rendering More...
|
| |
| virtual TBuilder | Vertical (bool vertical) |
| | True to distribute contained controls across columns, completely filling each column top to bottom before starting on the next column. The number of controls in each column will be automatically calculated to keep columns as even as possible. The default value is false, so that controls will be added to columns one at a time, completely filling each row left to right before starting on the next row. More...
|
| |
| virtual TBuilder | SetValue (Dictionary< string, object > values) |
| |


Prime Minister Boris Johnson 'looking at' getting rid of the BBC licence fee
Could it be under threat?
by Paul HironsAs the build-up to Thursday's General Election continues, Prime Minister Boris Johnson has dropped a bombshell on the campaign trail.
Mr Johnson, 55, asked a crowd during a stop in the northeast of England how long can [the BBC] justify the licence fee system, suggesting that the annual payment might be looked at closely if he retains power.
Read more: Peter Kay 'in talks with the BBC' following break from showbiz
The corporation came under fire earlier this year when it announced that over 75s will have to pay for their licence fees from next year.
Today (9.12.19) Mr Johnson was asked what his opinion on the licence fee was.
"Well, I don't think at this late stage in the campaign I'm going to make an unfunded spending commitment like that, but what I certainly think is that the BBC should cough up and pay for the licences for the over-75s as they promised to do," he replied.
"But at this stage we are not planning to get rid of all TV licence fees, though I am certainly looking at it."
He continued: "But you have to ask yourself whether that kind of approach to funding a TV, a media organisation still makes sense in the long-term given the way other organisations manage to fund themselves - that's all I will say.
"I think that the system of funding by what is effectively a general tax, isn't it, everybody has a TV, it bears reflection - let me put it that way.
"How long can you justify a system whereby everybody who has a TV has to pay to fund a particular set of TV and radio channels - that is the question."
Read more: Forensic science thriller stars Line Of Duty's Martin Compston
Back in October, Culture Secretary Nicky Morgan was asked about the possibility of the Beeb changing from its current system to a subscription service.
She told the committee: "I would need to understand what that would do to their income. I think the BBC is a very important institution, it's an enormous part of our soft power around the world, but undoubtedly the sector is changing
"We all know from streaming services and the way the younger generation consumes services, it is going to change."
Leave us a comment on our Facebook page @EntertainmentDailyFix and let us know what you think of this story.


Fitness trainer
I know from personal experience what benefits come from having a healthy and active lifestyle. My goal is to help others enjoy the same sense of accomplishment and well being that sport and exercise bring.
Fitness trainer
My aim is to ensure you enjoy your workouts! Having fun when working out is the first step towards sustainable life changes. I believe that progress is achieved by setting goals and working towards them together.
Fitness trainer
I enjoy training and pushing myself to become stronger and fitter, and I’m here to help you achieve the same thing!
Fitness Instructor Level 2
I’m here to share the benefit of my experience and incorporate weights into sessions to help you achieve your goals.
Yoga trainer
Exercise is powerful, it can make us stand a little taller, think a little clearer, and feel better.
Fitness trainer
I have worked as a fitness instructor, Strength and Conditioning coach and a personal trainer.
Fitness trainer
I know from personal experience what benefits come from having a healthy and active lifestyle.
Fitness trainer
I know from personal experience what benefits come from having a healthy and active lifestyle.


FREE QUOTE
800.669.3848
In the U.S., a burglary occurs
every 18 seconds
Keep an eye on your home before others do
Call Now:<PHONE_NUMBER>


Order online, jump the line and save time.Place your order on-the-go. Experience the euphoria of skipping those dreadful queues with the click of a button.
Perfect for the days your wallet is empty and the thought of running to the closest ATM gives you the jitters. We understand your laziness. Place your order online without moving an inch through Secure Payment Gateway Systems.
Enjoy the perks of being a regular at your food court by availing special offers from your favourite brands. Now your meal could get you those fancy shades you've always wanted!
Connect with us


This is an application that take's a user's birthday and calculates the day of the week they were born and then depending on their gender outputs their akan name.This akan names are derived from Ghanian culture who are mostly named the first day of the week they were born either female or male.


--- layout: post title: "Fortunately, I don't squash my commits" description: "The story of a bug, and how I addressed it." date: 2020-10-05 6:00 UTC tags: [Productivity, ASP.NET MVC, ASP.NET Web API] --- {% include JB/setup %}
{{ page.description }}
Okay, I admit it: I could have given this article all sorts of alternative titles, each of which would have made as much sense as the one I chose. I didn't want to go with some of the other titles I had in mind, because they would give it all away up front. I didn't want to spoil the surprise.
I recently ran into this bug, it took me hours to troubleshoot it, and I was appalled when I realised what the problem was.
This is the story of that bug.
There are several insights from this story, and I admit that I picked the most click-baity one for the title.
I was working on the umpteenth variation of an online restaurant reservations system, and one of the features I'd added was a schedule only visible to the maître d'. The schedule includes a list of all reservations for a day, including guests' email addresses and so on. For that reason, I'd protected that resource by requiring a valid JSON Web Token (JWT) with an appropriate role.
The code discussed here is part of the sample code base that accompanies my book Code That Fits in Your Head.
I'd deployed a new version of the API and went for an ad-hoc test. To my surprise, that particular resource didn't work. When I tried to request it, I got a 403 Forbidden
response.
"That's odd," I though, "it worked the last time I tried this."
The system is set up with continuous deployment. I push master to a remote repository, and a build pipeline takes over from there. It only deploys the new version if all tests pass, so my first reaction was that I might have made a mistake with the JWT.
I wasted significant time decoding the JWT and comparing its contents to what it was supposed to be. I couldn't find any problems.
I also meticulously compared the encryption key I'd used to sign the JWT with the key on the server. They were identical.
Incredulous, and running out of ideas, I tried running all tests on my development machine. Indeed, all 170 tests passed.
Finally, I gave up and ran the API on my development machine. It takes all of a 30 seconds to configure the code to run in that environment, so you're excused if you wonder why I didn't already do that. What can I say? I've almost two decades of experience with automated test suites. Usually, if all tests pass, the problem is environmental: a network topology issue, a bad or missing connection string, a misconfigured encryption key, an invalid JWT, things like that.
To my surprise, the problem also manifested on my machine.
Okay, even with hundreds of tests, perhaps some edge case went unnoticed. The only problem with that hypothesis was that this was hardly an edge case. I was only making a GET
request with a Bearer
token. I wasn't going through some convoluted sequence of steps.
GET /restaurants/1/schedule/2020/9/30 HTTP/1.1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5c[...]
I expected a successful response containing some JSON, but the result was 403 Forbidden
. That was the same behaviour I saw in the production environment.
Now, to be clear, this is indeed a protected resource. If you present an invalid JWT, 403 Forbidden
is the expected response. That's why I wasted a few hours looking for problems with the the JWT.
I finally, hesitatingly, concluded that the problem might be somewhere else. The JWT looked okay. So, hours into my troubleshooting I reluctantly set a breakpoint in my code and started the debugger. It isn't rational, but I tend to see it as a small personal defeat if I have to use the debugger. Even so, if used judiciously, it can be an effective way to identify problems.
The debugger never hit my breakpoint.
To be clear, the beginning of my Controller method looked like this:
[Authorize(Roles = "MaitreD")] [HttpGet("restaurants/{restaurantId}/schedule/{year}/{month}/{day}")] public async Task<ActionResult> Get( int restaurantId, int year, int month, int day) { if (!AccessControlList.Authorize(restaurantId)) return new ForbidResult();
My breakpoint was on the first line (the if
conditional), but the debugger didn't break into it. When I issued my GET
request, I immediately got the 403 Forbidden
response. The breakpoint just sat there in the debugger, mocking me.
When that happens, it's natural to conclude that the problem occurs somewhere in the framework; in this case, ASP.NET. To test that hypothesis, I commented out the [Authorize]
attribute and reissued the GET
request. My hypothesis was that I'd get a 200 OK
response, since the attribute is what tells ASP.NET to check authorisation.
The hypothesis held. The response was 200 OK
.
I hate when that happens. It's up there with fixing other people's printers. The problem is in the framework, not in my code. I didn't have any authorisation callbacks registered, so I was fairly certain that the problem wasn't in my code.
I rarely jump to the conclusion that there's a bug in the framework. In my experience, select is rarely broken. My new hypothesis had to be that I'd somehow managed to misconfigure the framework.
But where? There were automated tests that verified that a client could request that resource with a valid JWT. There were other automated tests that verified what happened if you presented an invalid JWT, or none at all. And all tests were passing.
While I was fiddling with the tests, I eventually ran a parametrised test by itself, instead of the entire test suite:
[Theory] [InlineData( "Hipgnosta", 2024, 11, 2)] [InlineData( "Nono", 2018, 9, 9)] [InlineData("The Vatican Cellar", 2021, 10, 10)] public async Task GetRestaurantScheduleWhileAuthorized( string name, int year, int month, int day)
This parametrised test has three test cases. When I ran just that test method, two of the test cases passed, but one failed: the Nono case, for some reason I haven't yet figured out.
I didn't understand why that test case ought to fail while the others succeeded, but I had an inkling. I commented out the Nono test case and ran the test method again.
One passed and one failing test.
Now The Vatican Cellar test case was failing. I commented that out and ran the test method again. The remaining test case failed.
This reeks of some sort of test interdependency. Apparently, something happens during the first test run that makes succeeding tests pass, but it happens too late for the first one. But what?
Then something occurred to me that I should have thought of sooner. This feature used to work. Not only had the tests been passing, but I'd actually interacted with the deployed service, presenting a valid JWT and received a 200 OK
response.
Once than dawned on me, I realised that it was just a manner of performing a binary search. Since, of course, I use version control, I had a version I knew worked, and a version that didn't work. The task, then, was to find the commit that introduced the defect.
As I've already implied, the system in question is an example code base. While I have a cloud-based production environment, none but I use it. It had been four or five days since I'd actually interacted with the real service, and I'd been busy making changes, trusting exclusively in my test suite. I tend to make frequent, small commits instead of big, infrequent commits, so I had accumulated about a hundred and fifty commits since the 'last known good' deployment.
Searching through hundreds of commits sounds overwhelming, but using binary search, it's actually not that bad. Pick the commit halfway between the 'last known good' commit and the most recent commit, and check it out. See if the defect is present there. If it is, you know that it was introduced somewhere between the commit you're looking at, and the 'last known good' commit. If it isn't present, it was introduced later. Regardless of the outcome, you know in which half to look. You now pick a new commit in the middle of that set and repeat the exercise. Even with, say, a hundred commits, the first bisection reduces the candidate set to 50, the next bisection to 25, then 13, then 7, 4, 2, and then you have it. If you do this systematically, you should find the exact commit in less than eight iterations.
This is, as far as I understand it, the algorithm used by Git bisect. You don't have to use the bisect
command - the algorithm is easy enough to do by hand - but let's see how it works.
You start a bisect
session with:
mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((93c6c35...)) $ git bisect start mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((93c6c35...)|BISECTING)
This starts an interactive session, which you can tell from the Git integration in Git Bash (it says BISECTING
). You now mark a commit as being bad:
$ git bisect bad mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((93c6c35...)|BISECTING)
If you don't provide a commit ID at that point, Git is going to assume that you meant that the current commit (in this case 93c6c35
) is bad. That's what I had in mind, so that's fine.
You now tell it about a commit ID that you know is good:
$ git bisect good 7dfdab2 Bisecting: 75 revisions left to test after this (roughly 6 steps) [1f78c9a90c2088423ab4fc145b7b2ec3859d6a9a] Use InMemoryRestaurantDatabase in a test mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((1f78c9a...)|BISECTING)
Notice that Git is already telling us how many iterations we should expect. You can also see that it checked out a new commit (1f78c9a
) for you. That's the half-way commit.
At this point, I manually ran the test method with the three test cases. All three passed, so I marked that commit as good:
$ git bisect good Bisecting: 37 revisions left to test after this (roughly 5 steps) [5abf65a72628efabbf05fccd1b79340bac4490bc] Delete Either API mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((5abf65a...)|BISECTING)
Again, Git estimates how many more steps are left and checks out a new commit (5abf65a
).
I repeated the process for each step, marking the commit as either good or bad, depending on whether or not the test passed:
$ git bisect bad Bisecting: 18 revisions left to test after this (roughly 4 steps) [fc48292b0d654f4f20522710c14d7726e6eefa70] Delete redundant Test Data Builders mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((fc48292...)|BISECTING) $ git bisect good Bisecting: 9 revisions left to test after this (roughly 3 steps) [b0cb1f5c1e9e40b1dabe035c41bfb4babfbe4585] Extract WillAcceptUpdate helper method mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((b0cb1f5...)|BISECTING) $ git bisect good Bisecting: 4 revisions left to test after this (roughly 2 steps) [d160c57288455377f8b0ad05985b029146228445] Extract ConfigureClock helper method mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((d160c57...)|BISECTING) $ git bisect good Bisecting: 2 revisions left to test after this (roughly 1 step) [4cb73c219565d8377aa67d79024d6836f9000935] Compact code mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((4cb73c2...)|BISECTING) $ git bisect good Bisecting: 0 revisions left to test after this (roughly 1 step) [34238c7d2606e9007b96b54b43e678589723520c] Extract CreateTokenValidationParameters method mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((34238c7...)|BISECTING) $ git bisect bad Bisecting: 0 revisions left to test after this (roughly 0 steps) [7d6583a97ff45fbd85878cecb5af11d93213a25d] Move Configure method up mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((7d6583a...)|BISECTING) $ git bisect good 34238c7d2606e9007b96b54b43e678589723520c is the first bad commit commit 34238c7d2606e9007b96b54b43e678589723520c Author: Mark Seemann<EMAIL_ADDRESS>Date: Wed Sep 16 07:15:12 2020 +0200 Extract CreateTokenValidationParameters method Restaurant.RestApi/Startup.cs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((7d6583a...)|BISECTING)
Notice that the last step finds the culprit. It tells you which commit is the bad one, but surprisingly doesn't check it out for you. You can do that using a label Git has created for you:
$ git checkout refs/bisect/bad Previous HEAD position was 7d6583a Move Configure method up HEAD is now at 34238c7 Extract CreateTokenValidationParameters method mark@Vindemiatrix MINGW64 ~/Documents/Redacted/Restaurant ((34238c7...)|BISECTING)
You've now found and checked out the offending commit. Hopefully, the changes in that commit should give you a clue about the problem.
What's in that commit? Take a gander:
$ git show commit 34238c7d2606e9007b96b54b43e678589723520c (HEAD, refs/bisect/bad) Author: Mark Seemann<EMAIL_ADDRESS>Date: Wed Sep 16 07:15:12 2020 +0200 Extract CreateTokenValidationParameters method diff --git a/Restaurant.RestApi/Startup.cs b/Restaurant.RestApi/Startup.cs index 6c161b5..bcde861 100644 --- a/Restaurant.RestApi/Startup.cs +++ b/Restaurant.RestApi/Startup.cs @@ -79,10 +79,6 @@ namespace Ploeh.Samples.Restaurants.RestApi private void ConfigureAuthorization(IServiceCollection services) { - JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - - var secret = Configuration["JwtIssuerSigningKey"]; - services.AddAuthentication(opts => { opts.DefaultAuthenticateScheme = @@ -91,15 +87,8 @@ namespace Ploeh.Samples.Restaurants.RestApi JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(opts => { - opts.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey( - Encoding.ASCII.GetBytes(secret)), - ValidateIssuer = false, - ValidateAudience = false, - RoleClaimType = "role" - }; + opts.TokenValidationParameters = + CreateTokenValidationParameters(); opts.RequireHttpsMetadata = false; }); @@ -108,6 +97,23 @@ namespace Ploeh.Samples.Restaurants.RestApi sp.GetService<IHttpContextAccessor>().HttpContext.User)); } + private TokenValidationParameters CreateTokenValidationParameters() + { + JwtSecurityTokenHandler.DefaultMapInboundClaims = false; + + var secret = Configuration["JwtIssuerSigningKey"]; + + return new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey( + Encoding.ASCII.GetBytes(secret)), + ValidateIssuer = false, + ValidateAudience = false, + RoleClaimType = "role" + }; + } + private void ConfigureRepository(IServiceCollection services) {
Can you spot the problem?
As soon as I saw that diff, the problem almost jumped out at me. I'm so happy that I make small commits. What you see here is, I promise, the entire diff of that commit.
Clearly, you're not familiar with this code base, but even so, you might be able to intuit the problem from the above diff. You don't need domain-specific knowledge for it, or knowledge of the rest of the code base.
I'll give you a hint: I moved a line of code into a lambda expression.
In the above commit, I extracted a helper method that I called CreateTokenValidationParameters
. As the name communicates, it creates JWT validation parameters. In other words, it supplies the configuration values that determine how ASP.NET figures out how to authorise an HTTP request.
Among other things, it sets the RoleClaimType
property to "role"
. By default, the [Authorize]
attribute is looking for a role claim with a rather complicated identifier. Setting the RoleClaimType
enables you to change the identifier. By setting it to "role"
, I'm telling the ASP.NET framework that it should look for claims named role
in the JWT, and these should be translated to role claims.
Except that it doesn't work.
Ostensibly, this is for backwards compatibility reasons. You have to set JwtSecurityTokenHandler.DefaultMapInboundClaims to false
. Notice that this is a global variable.
I thought that the line of code that does that conceptually belongs together with the other code that configures the JWT validation parameters, so I moved it in there. I didn't think much about it, and all my tests were still passing.
What happened, though, was that I moved mutation of a global variable into a helper method that was called from a lambda expression. Keep in mind that lambda expressions represent code that may run later; that execution is deferred.
By moving that mutation statement into the helper method, I inadvertently deferred its execution. When the application's Startup
code runs, it configures the service. All the code that runs inside of AddJwtBearer
, however, doesn't run immediately; it runs when needed.
This explains why all tests were passing. My test suite has plenty of self-hosted integration tests in the style of outside-in test-driven development. When I ran all the tests, the deferred code block would run in some other test context, flipping that global bit as a side effect. When the test suite reaches the test that fails when run in isolation, the bit is already flipped, and then it works.
It took me hours to figure out what the problem was, and it turned out that the root cause was a global variable.
Global variables are evil. Who knew?
Once you figure out what the problem is, you should reproduce it with an automated test.
Yes, and how do I do that here? I already had tests that verify that you can GET
the desired resource if you present a valid JWT. And that test passes!
The only way I can think of to reproduce the issue with an automated test is to create a completely new, independent test library with only one test. I considered doing that, weighed the advantages against the disadvantages and decided, given the context, that it wasn't worth the effort.
That means, though, that I had to accept that within the confines of my existing testing strategy, I can't reproduce the defect. This doesn't happen to me often. In fact, I can't recall that it's ever happened to me before, like this.
It took me hours to find the bug, and ten seconds to fix it. I just moved the mutation of JwtSecurityTokenHandler.DefaultMapInboundClaims
back to the ConfigureAuthorization
method:
diff --git a/Restaurant.RestApi/Startup.cs b/Restaurant.RestApi/Startup.cs index d4917f5..e40032f 100644 --- a/Restaurant.RestApi/Startup.cs +++ b/Restaurant.RestApi/Startup.cs @@ -79,6 +79,7 @@ namespace Ploeh.Samples.Restaurants.RestApi private void ConfigureAuthorization(IServiceCollection services) { + JwtSecurityTokenHandler.DefaultMapInboundClaims = false; services.AddAuthentication(opts => { opts.DefaultAuthenticateScheme = @@ -99,8 +100,6 @@ namespace Ploeh.Samples.Restaurants.RestApi private TokenValidationParameters CreateTokenValidationParameters() { - JwtSecurityTokenHandler.DefaultMapInboundClaims = false; - var secret = Configuration["JwtIssuerSigningKey"]; return new TokenValidationParameters
An ad-hoc smoke test verified that this solved the problem.
There are multiple insights to be had from this experience:


{% extends 'templates/nhs_transaction_layout.html' %} {% block pageTitle %}NHS.UK prototyping kit{% endblock %} {% block content %} NHS e-Referral Service Prototypes Book an Appointment This is a new service. We're still working on it. Send feedback to<EMAIL_ADDRESS>to help us improve it. The live service is www.ebs.ncrs.nhs.uk {% endblock %}
This is a new service. We're still working on it. Send feedback to<EMAIL_ADDRESS>to help us improve it. The live service is www.ebs.ncrs.nhs.uk


To get started you only need to add two classes. First add the animated
class. Second class is the actual animation, to find what will work for you simply test and pick one from the list above. My favorites animation families are slideIn
, fadeIn
, bounceIn
.
In the example below I'm fading in the Wunderlist logo
<% code do %> <% end %>Sometimes you want an animation to continue forever. So just add infinite
and it'll never stop.
If you want to add a delay, simply add the class delay
We have three durations at the moment. Fast (0.5s), Normal (Default, 1s), Slow (2s).
<% code do %> <% end %> <% code do %> <% end %> <% code do %> <% end %>


This is a summary of the last round.
The balls you saw were
.

The balls the automated player 1 shared were
.
The balls the automated player 2 shared were
.
The true color of the urn was
.
Your vote was
.
The comittee's decision was
and it was
.
Your resulting payoff for this round is
ECU
. If this round is randomly selected, the payoff will be added to your final bonus.
Click 'Continue' to continue to the next round.


{% extends "base.html" %} {% block body %} MBTA Green Line Accessibility Find a stop and see which trolley cars are accessible. {% for stop in stops %} {{ stop }} {% endfor %} {% endblock %} {% block includes %} {% endblock %}


Networks
COMPAS networks are simple edge graphs: they consist of vertices connected by edges. Not all vertices have to be connected by edges. A network without edges is a valid network. In fact, even a network without vertices and edges is a valid network, albeit a quite pointless one.
Edges have a direction. There can only be one edge between two vertices in a particular direction. However, there can be two edges between two vertices in opposite direction. Vertices can be connected to themseleves.
Check out the docs for detailed information about the network and the available
functionality: compas.datastructures.Network
.
Making a network
from compas.datastructures import Network
network = Network()
Adding vertices and edges
>>> a = network.add_vertex()
>>> b = network.add_vertex(x=1.0)
>>> c = network.add_vertex(y=1.0)
>>> d = network.add_vertex(x=-1.0)
>>> e = network.add_vertex(y=-1.0)
>>> network.add_edge(a, b)
(0, 1)
>>> network.add_edge(a, c)
(0, 2)
>>> network.add_edge(a, d)
(0, 3)
>>> network.add_edge(a, e)
(0, 4)
Identifiers
All vertices in a network have a unique id, the key of the vertex. By default, keys are integers, and every vertex is assigned a number corresponding to the order in which they are added. The number is always the highest number used so far, plus one.
Other types keys may be specified as well, as long as their value is hashable.
>>> print(a, type(a))
0 <class 'int'>
>>> b == a + 1
True
>>> f = network.add_vertex(key=7)
>>> f == e + 1
False
>>> g = network.add_vertex()
>>> g == f + 1
True
>>> network.add_vertex(key='compas')
'compas'
>>> network.add_vertex()
9
Data
Iteration
>>> network.vertices()
<dict_keyiterator at 0x6193a2958>
>>> network.edges()
<generator object Network.edges at 0x61560f678>
>>> for key in network.vertices():
... print(key)
...
0
1
2
3
4
7
8
compas
9
>>> for u, v in network.edges():
... print(u, v)
...
0 1
0 2
0 3
0 4
Lists
>>> list(network.vertices())
[0, 1, 2, 3, 4, 7, 8, 'compas', 9]
>>> list(network.edges())
[(0, 1), (0, 2), (0, 3), (0, 4)]
Filtering
>>> network.vertices_where({'x': 0.0})
<generator object VertexFilter.vertices_where at 0x61560f468>
Attributes
All vertices and edges automatically have the default attributes. The
default vertex attributes are xyz coordinates, with x=0
, y=0
and
z=0
. Edges have no defaults.
To change the default attributes associated with vertices and edges, do:
>>> network.update_default_vertex_attributes({'z': 10}, is_fixed=False)
>>> network.update_default_vertex_attributes(z=10, is_fixed=False)
>>> network.update_default_edge_attributes({'weight': 0.0})
>>> network.update_default_edge_attributes(weight=0.0)
Note
Other attributes then the ones specified in the defaults can also be
added. However, these attributes then only exist on the vertices or
edges where they have been specified. To prevent this and only allow the
registered attributes to be added, set
Network.strict_attributes = True
.
When a vertex or edge is added to the network, the default attributes
are copied and the values of the specified attributes are modified. To
only store the modified values, set Network.copy_defaults = False
.
Getting attributes
>>> network.get_vertex_attribute(a, 'is_fixed')
False
>>> network.get_vertices_attribute('x')
[0.0, 1.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.0]
>>> network.get_vertices_attributes('xyz')
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0],
[0.0, -1.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0],
[0.0, 0.0, 0.0]]
Setting attributes
>>> network.set_vertex_attribute(a, 'is_fixed', True)
>>> network.set_vertices_attribute('is_fixed', True)
>>> network.set_vertices_attributes(('z', 'is_fixed'), (3, False))
Using constructors
# network = Network.from_data(data)
# network = Network.from_lines([([], []), ([], [])])
# network = Network.from_json('network.json')
# network = Network.from_pickle('network.pickle')
>>> network = Network.from_obj(compas.get('lines.obj'))
Note
COMPAS provides sample data for debugging purposes.
This data can be accessed using compas.get()
.
From/To
>>> network = Network.from_obj(compas.get('lines.obj'))
>>> data = network.to_data()
>>> other = Network.from_data(data)
Queries
Visualisation
To create a 2D representation of a network, use a plotter.
from compas_plotters import NetworkPlotter