250 likes | 271 Views
Learn how the Interceptor architectural pattern enhances modularity, flexibility, and extensibility in software development. Understand its implementation, benefits, and use cases.
E N D
E81 CSE 532S: Advanced Multi-Paradigm Software Development Interceptor Pattern Venkita Subramonian, Christopher Gill, Morgan Deters, Anand Krishnan Department of Computer Science and Engineering Washington University, St. Louis cdgill@cse.wustl.edu
Overview • Abstraction • Genericity • Conformity • Extensibility The Interceptor architectural pattern allows services to be added transparently to a framework and triggered automatically when certain events occur. • Reusability • Modularity • Flexibility
Motivation int send_data(Buffer *buf, size_t n) { log(buf, n); if(!check_security(buf, n) return 0; encrypt(buf, n); // etc... finally_actually_send_data(buf, n); return 1; } • Simple client/server • add logging features • add security checks • add encryption • add QoS/prioritization • add special-case or exception handling • E.g., traffic overload, administrator access, etc. • Want to add new processing for existing events • Code easily becomes tangled and unmanageable • Want to add new processing for new events • Adding a new interception point is error-prone
Problem • Fundamental concern • How to integrate out-of-band tasks with in-band tasks? • Straightforward (and all to common) approach • Paste the out-of-band logic wherever it’s needed • May be multiple places to paste it • Brittle, tedious, error-prone, time/space (e.g., inlining) • Is there a better and more general approach? Out-of-Band (Admin) In-Band (Client) In-Band (Server)
Interceptor Solution Approach • Our goal is to find a general mechanism to integrate out-of-band tasks with in-band tasks • In-band tasks • Processed as usual via framework • Out-of-band tasks • register with framework via special interfaces • are triggered by framework on certain events • Some events are generated by in-band processing • are provided access to framework internals (i.e., context) via specific interfaces
Interceptor Interaction diagram Application Concrete Framework Concrete Interceptor Dispatcher create create attach Concrete Interceptor event create Context Object callback iterate_list Context object callback get_value access_internals
Implementation • Model internal behavior of concrete framework • Identify and model interception points • Identify concrete framework state transitions • Partition interception points into reader/writer sets • Integrate them into state machine model • Partition them into disjoint interception groups • Specify the context object • semantics • number of context object types • strategy for passing context objects to interceptors • per-registration vs. per-event
Implementation (continued) • Specify the interceptors • Specify the dispatchers • interceptor registration interface • dispatcher callback interface • Implement call-back mechanisms in concrete framework • Implement concrete interceptors
Example Pattern Language Sketch • Data streams need transparent security protection • Apply an Interceptor to encode and decode stream (insert an object into a path of the system) • Smart adversary might learn en/decoding in use • Apply ACT to identify new en/decoding schemes at run-time • Triggering changes may complicate threading • Apply Reactor for in-and-out-of-band processing • Need to trigger changes on remote applications • Apply Acceptor/Connector to set up connection to a remote interceptor dispatcher • Any point in the code might detect a need to change • Apply Singleton to interceptor dispatcher • Make these dispatchers remote Observers of one another
Design Outline • Multi-threaded and/or reactive concurrency with streams of text (as in lab assignments) • Add encode/decode interceptor capabilities • XOR, +k, -k, *k, /k, rotate(k), IDEA, RSA • Design the interceptors out-of-band interfaces • ACTs for encoding/decoding function and argument • Initial encoding function and argument • Design interceptor dispatcher and registry • Singleton for all interceptors in a process • Event handler for reactor, also an acceptor • How to federate across distribution boundaries?
“Dynamic” Interceptor Variant • Base Interceptor • virtual apply_XXX() • Concrete Interceptors • Logging • Checksum • Statistics • Encrypt • security checks class Interceptor { public: virtual void apply_encode(string&) = 0; virtual void apply_decode(string&) = 0; };
The Data_Endpoint class • Abstraction for sending and receiving data • Two Data_Endpoints connect then send • send_data() and receive_data() both dispatch to interceptors • add_interceptor() registers new concrete interceptors
“Dynamic” Interceptor (1/3) // A simple Interceptor Function class that rot13's a message.class Rot13_Interceptor : public Interceptor { class Rot13 : public unary_function<char, char> { public: char operator()(char c) { if(islower(c)) return c + ((c >= 'a' + 13) ? -13 : 13); if(isupper(c)) return c + ((c >= 'A' + 13) ? -13 : 13); return c; } };public: void apply_encode(string& s) { transform(s.begin(), s.end(), s.begin(), Rot13()); } void apply_decode(string& s) { transform(s.begin(), s.end(), s.begin(), Rot13()); }};
“Dynamic” Interceptor (2/3) class Data_Endpoint { Data_Endpoint *peer_; string *data_; typedef vector<Interceptor*> Interceptor_List; Interceptor_List interceptors_; // … public: // … void send_data(const string& s) { // not mt-safe! Encode_Dispatcher dispatch(s); for_each(interceptors_.begin(), interceptors_.end(), dispatch); string t = dispatch.get_result(); //send data to peer_ cout << "Data_Endpoint@" << this << " sent '" << t << "' on the wire" << endl; } // …}
“Dynamic” Interceptor (3/3) class Encode_Dispatcher : public unary_function<Interceptor*, void> { string s_;public: Encode_Dispatcher(string s) : s_(s) { } void operator()(Interceptor* i) { i->apply_encode(s_); } string get_result(void) { return s_; }};
Using “Dynamic” Interceptor Data_Endpoint foo; Data_Endpoint bar; Print_Interceptor *pf = new Print_Interceptor; Rot13_Interceptor *rot13 = new Rot13_Interceptor; foo.add_interceptor(pf); foo.add_interceptor(rot13); foo.add_interceptor(pf); bar.add_interceptor(pf); bar.add_interceptor(rot13); bar.add_interceptor(pf); foo.connect(bar); foo.send_data(“a string”);
Analysis of Dynamic Interceptor • apply_XXX() design is fragile • To add a new interception type: • add pure virtual apply_foo() to base • implement in all existing concrete interceptors • or refactor to have a single apply() with a switch • May not need all of this flexibility • may not need per-event context objects • may not need to reorder or add interceptors at runtime
Consequences + Separation of Concerns + (Future) Flexibility +Reusability/Portability −Efficiency/Heterogenity −Evil Interceptors
Interceptors using Generics • Interceptor instances may not be needed (stateless) • No context objects • Note: one interceptor • Can’t do both checksum and encrypt directly here • Could compose both functions into interceptor • Or, could publish trigger event which other interceptors can handle Can specialize! template <class Interceptor> class Data_Endpoint { public: send() { ... Interceptor()(Send_Event()); ... } ... }; class Logging_Interceptor { public: void operator()(...) { ... } };
Potential Problems • Data_Endpoint incompatibility • arises from instantiating the Data_Endpoint template with different types • connect() and send() no longer work • Solution: template <class Other_Interceptor> friend class Data_Endpoint; • Single Interceptor only • Can use Typelists to extend this approach!
Typelists • List of types using templates • No inherent runtime overhead • Allows arbitrary number of Interceptors: template <class T, class U>struct Typelist { typedef T Head; typedef U Tail; }; Typelist<Logging_Interceptor, Typelist<Checksum_Interceptor, NullType> > Andrei Alexandrescu, Modern C++ Design: Generic Programming and Design Patterns Applied
More on Typelists • Telescoping Typelist definitions awkward… • typelist.h defines: • Mechanism includes various algorithms: • Length, TypeAt (index), Append, Erase (type from Typelist), Replace (type with type), DerivedToFront • all “for free” – evaluated at compile time! #define TYPELIST_1(T1) Typelist<T1,NullType> #define TYPELIST_2(T1,T2) Typelist<T1,TYPELIST_1(T2) > #define TYPELIST_3(T1,T2,T3) Typelist<T1,TYPELIST_2(T2,T3) > ... up to TYPELIST_50(...)
Using Generic Interceptors Data_Endpoint<Chain_Interceptor< TYPELIST_3(Print_Interceptor, Rot13_Interceptor, Print_Interceptor)> > foo; Data_Endpoint<Chain_Interceptor< TYPELIST_1(Rot13_Interceptor)> > bar; foo.connect(bar); foo.send_data(“Hello world”);
Analysis of Generic Interceptors • No runtime modification of Interceptors • BUT:efficient (inlined) dispatch • Performance vs. Flexibility • Because of template use, it’s “compile-time configurable and flexible” • easy to change Interceptor attachment • but must recompile