Customize GCDWebServer default download webpage with method swizzling

Currently I am developing a simple voice memo iOS app. One basic feature is to let users download their recorded memos from any browser. One way to do this is to set up a http server running inside the app to provide a file download feature.

I googled some solutions and found that GCDWebServer was a very popular lightweight http server library for iOS apps. Although it only has an Objective C version, it can be easily integrated into a Swift project via CocoaPods. It is also very easy to set up a http server via GCDWebUploader using just a few lines of code:

You can get the address information from the IDE debug window after the server is started. Then when you access this address via any browser, you will see a web page that looks like this:

Default web page of GCDWebUploader

Could the solution really be this easy? It seemed to be a piece of cake. I was initially pretty satisfied but then I realized there were several problems. The default web page provides basic file management features but they were too many for my app. What I need is only to allow users to download files without changing anything else. Because I save voice memo file information in a database, if users can upload files, create folders, or delete or rename files, it will make them inconsistent with the information in the database and mess up the app’s functioning.

I checked the source code and found GCDWebUploader did have some hook methods to prevent it:

GCDWebUploader hook methods

If you set No/false to return the values of all of these hook methods, it will display a warning when users try to do some editing:

Warning when users try to delete a file

Although this was a workable solution, the webpage displayed was not very visually appealing.

For my voice memo app, simplicity is paramount. I want to provide users with a neat webpage and hide any unnecessary buttons. Since I do not want to bother to modify the source code for the time being, if I can modify the web page content before it returns to requests from browsers instead, this should work too. I checked the source code again and found the html content for the web page is returned by initial method initWithHTML:(NSString*)html in GCDWebServerDataResponse.m

Initial method for returning html content

So if we can modify the html content here, then it will be possible to resolve this issue. This is where method swizzling comes in.

You may have used this black magic many times since the Objective C era. The way to use method swizzling changes a little bit in Swift, but you can still use it. Check out this StackOverflow link to read about the different ways to use method swizzling in Swift 3.0 and Swift 4.0:

What you need to do next is just add the GCDWebServerDataResponse extension and put your method swizzling inside. Below is my version:

I used SwiftSoup (A Pure Swift HTML Parser) to help parse html quickly. I use html attribute “display: none” to hide unnecessary DOM elements. Since the code for the delete and edit buttons is implemented by javaScript, SwiftSoup cannot parse them correctly. I end up using string replacement to solve the problem.

One thing you should notice is that when you create the method (swizzled_init(html:)) to replace the original method (init(html:)), you should let it return to Self since the original method is an initialized method, otherwise the magic will not work.

Then we can start the http server and check our download page again. It now appears like this:

Much neater, right? Now I can return to drinking my tea and coding in peace without worrying about any unexpected user modifications.

Since you can replace the html content, you can even change the entire webpage into your own implementation if you want. But you have to understand the source code well enough and make sure that your own page works as expected.

This solution comes with its pros and cons.

Pros: It is non-invasive and easy to implement and very flexible.

Cons: It relies on the inside logic of the CGDWebServer. If the logic is modified in any future version, then you have to modify your code too. You also have to do some extra work to parse html.

Maybe the ultimate way to solve the problem is to modify the source code of GCDWebSever and create a pull request on Github and hope the author approves the modification, but that is another story and may not solve your problem in time.

Good luck!